akanjs 3.1.0-rc.0 → 3.1.0-rc.1

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.
Files changed (36) hide show
  1. package/client/csrTypes.ts +4 -0
  2. package/dictionary/base.dictionary.ts +4 -0
  3. package/package.json +1 -1
  4. package/service/adapt.ts +1 -1
  5. package/store/action.ts +17 -19
  6. package/types/client/csrTypes.d.ts +3 -0
  7. package/types/dictionary/base.dictionary.d.ts +1 -1
  8. package/types/dictionary/dictionary.d.ts +3 -3
  9. package/types/service/adapt.d.ts +1 -1
  10. package/types/ui/Layout/BottomInset.d.ts +1 -1
  11. package/types/ui/Layout/Navbar.d.ts +1 -1
  12. package/types/ui/Layout/TopInset.d.ts +2 -2
  13. package/types/ui/Layout/index.d.ts +4 -4
  14. package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.d.ts +5 -4
  15. package/types/vendor/@akanjs/native/packages/desktop/src/dispatcher.d.ts +6 -0
  16. package/types/vendor/@akanjs/native/plugins/single-instance/src/socket.d.ts +1 -1
  17. package/types/vendor/use-agentic/AgentActivity.d.ts +3 -1
  18. package/types/vendor/use-agentic/AgenticSurface.d.ts +2 -2
  19. package/types/webkit/EmbeddedLayout.d.ts +10 -0
  20. package/types/webkit/RenderLayer.d.ts +3 -1
  21. package/ui/Layout/BottomInset.tsx +12 -2
  22. package/ui/Layout/Navbar.tsx +22 -7
  23. package/ui/Layout/TopInset.tsx +7 -3
  24. package/ui/Model/ViewEditModal.tsx +16 -10
  25. package/ui/Select.tsx +12 -7
  26. package/ui/System/CSR.tsx +7 -1
  27. package/ui/System/Client.tsx +1 -0
  28. package/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.ts +35 -10
  29. package/vendor/@akanjs/native/packages/desktop/src/dispatcher.ts +13 -5
  30. package/vendor/@akanjs/native/plugins/single-instance/src/desktop.ts +4 -0
  31. package/vendor/@akanjs/native/plugins/single-instance/src/socket.ts +12 -5
  32. package/vendor/use-agentic/AgentActivity.tsx +4 -2
  33. package/vendor/use-agentic/AgenticSurface.ts +3 -3
  34. package/webkit/CsrRouteTable.ts +8 -1
  35. package/webkit/EmbeddedLayout.tsx +77 -0
  36. package/webkit/RenderLayer.tsx +20 -5
@@ -364,6 +364,10 @@ export const usePathCtx = () => {
364
364
  return contextValues;
365
365
  };
366
366
 
367
+ /** Inside a layout's own markup that an MCP view hides while the page is drawn on its own: frame chrome draws nothing. */
368
+ export const hiddenLayoutContext = sharedContext<boolean>("hiddenLayout", false);
369
+ export const useInHiddenLayout = () => useContext(hiddenLayoutContext);
370
+
367
371
  export type PageActivity = "current" | "prev" | "pending" | "hidden";
368
372
  export interface PageActivityState {
369
373
  activity: PageActivity;
@@ -57,6 +57,10 @@ export const baseDictionary = serviceDictionary(["en", "ko"])
57
57
  "The same request is still being handled. Please try again in a moment.",
58
58
  "같은 요청을 아직 처리하고 있습니다. 잠시 후 다시 시도해주세요.",
59
59
  ],
60
+ endpointClosed: [
61
+ "{endpoint} is closed: the slice's guards name none that allows it.",
62
+ "{endpoint}이(가) 닫혀 있습니다. 슬라이스 guards에 이 동작을 허용하는 guard를 지정해야 합니다.",
63
+ ],
60
64
  })
61
65
  .translate({
62
66
  somethingWrong: ["Something's wrong!", "문제가 생겼어요!"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "3.1.0-rc.0",
3
+ "version": "3.1.0-rc.1",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
package/service/adapt.ts CHANGED
@@ -13,7 +13,7 @@ export type AdaptorCls<Methods = any, InjectMap extends Record<string, InjectInf
13
13
  { readonly [INJECT_META]: InjectMap; readonly refName: string }
14
14
  >;
15
15
 
16
- export function adapt<Name extends string>(name: Name): AdaptorCls;
16
+ export function adapt<Name extends string>(name: Name): AdaptorCls<{}>;
17
17
 
18
18
  export function adapt<Name extends string, Injection extends InjectBuilder<"use" | "env" | "memory" | "plug">>(
19
19
  name: Name,
package/store/action.ts CHANGED
@@ -510,6 +510,15 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
510
510
  const updatedAt = (model as unknown as { updatedAt?: { toISOString?: () => string } }).updatedAt;
511
511
  return typeof updatedAt?.toISOString === "function" ? updatedAt.toISOString() : null;
512
512
  };
513
+
514
+ const servedFetch = <T>(key: string) => {
515
+ const fn = fetch[key] as ((...args: any[]) => Promise<T>) | undefined;
516
+ if (typeof fn === "function") return fn;
517
+ throw Object.assign(new Error(`${key} is closed: the slice's guards name none that allows it`), {
518
+ error: "base.error.endpointClosed",
519
+ data: { endpoint: key },
520
+ });
521
+ };
513
522
  function armDraft(this: SetGet, draftScope: string | undefined, form: object, baseUpdatedAt: string | null) {
514
523
  openFormBase = draftScope ? form : null;
515
524
  if (!draftScope) return null;
@@ -598,7 +607,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
598
607
 
599
608
  if (!modelInput) return;
600
609
  this.set({ [names.modelLoading]: true });
601
- const model = await (fetch[names.createModel] as (...args: any[]) => Promise<Full>)(modelInput, { onError });
610
+ const model = await servedFetch<Full>(names.createModel)(modelInput, { onError });
602
611
  this.set({
603
612
  [names.modelForm]: immerify(modelRef, defaultModel),
604
613
  [names.modelDraft]: clearDraft(currentState[names.modelDraft] as DraftState | null),
@@ -623,11 +632,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
623
632
  const modelInput = (cnst.input.purify as (form: any) => DefaultOf<Input> | null)(modelForm);
624
633
  if (!modelInput) return;
625
634
  if (model?.id === modelForm.id) this.set({ [names.modelLoading]: modelForm.id });
626
- const updatedModel = await (fetch[names.updateModel] as (...args: any[]) => Promise<Full>)(
627
- modelForm.id,
628
- modelInput,
629
- { onError },
630
- );
635
+ const updatedModel = await servedFetch<Full>(names.updateModel)(modelForm.id, modelInput, { onError });
631
636
  this.set({
632
637
  ...(model?.id === updatedModel.id
633
638
  ? { [names.model]: updatedModel, [names.modelLoading]: false, [names.modelViewAt]: new Date() }
@@ -650,7 +655,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
650
655
  const modelInput = (cnst.input.purify as (data: any) => Input | null)(data);
651
656
  if (!modelInput) return;
652
657
  this.set({ [names.modelLoading]: true });
653
- const model = await (fetch[names.createModel] as (...args: any[]) => Promise<Full>)(modelInput, { onError });
658
+ const model = await servedFetch<Full>(names.createModel)(modelInput, { onError });
654
659
  this.set({
655
660
  [names.model]: model,
656
661
  [names.modelLoading]: false,
@@ -673,7 +678,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
673
678
  const modelInput = (cnst.input.purify as (data: any) => DefaultOf<Input> | null)(data);
674
679
  if (!modelInput) return;
675
680
  if (model?.id === id) this.set({ [names.modelLoading]: id });
676
- const updatedModel = await (fetch[names.updateModel] as (...args: any[]) => Promise<Full>)(id, modelInput, {
681
+ const updatedModel = await servedFetch<Full>(names.updateModel)(id, modelInput, {
677
682
  onError,
678
683
  });
679
684
  this.set({
@@ -689,10 +694,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
689
694
  },
690
695
  [names.removeModel]: async function (this: SetGet, id: string, options?: FetchPolicy & { modal?: string | null }) {
691
696
  const { modal, ...fetchPolicyOptions } = options ?? {};
692
- const model = await (fetch[names.removeModel] as (...args: any[]) => Promise<Full & { removedAt: Dayjs | null }>)(
693
- id,
694
- fetchPolicyOptions,
695
- );
697
+ const model = await servedFetch<Full & { removedAt: Dayjs | null }>(names.removeModel)(id, fetchPolicyOptions);
696
698
  const lightModel = new cnst.light().set(model) as unknown as Light;
697
699
  slices.forEach(({ keys: { state: namesOfSlice } }) => {
698
700
  const currentState = this.get() as { [key: string]: any };
@@ -776,7 +778,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
776
778
  ) {
777
779
  const id = typeof modelOrId === "string" ? modelOrId : modelOrId.id;
778
780
  this.set({ [names.modelFormLoading]: id, [names.modelModal]: modal ?? "edit" });
779
- const model = await (fetch[names.model] as (...args: any[]) => Promise<Full>)(id, { onError });
781
+ const model = await servedFetch<Full>(names.model)(id, { onError });
780
782
  const modelForm = immerify(modelRef, deepObjectify<Input>(model as unknown as Input) as object) as Input;
781
783
  this.set({
782
784
  [names.model]: model,
@@ -798,11 +800,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
798
800
  const currentState = this.get() as { [key: string]: any };
799
801
  const model = currentState[names.model] as Full | null;
800
802
  if (id === model?.id) this.set({ modelLoading: id });
801
- const updatedModel = await (fetch[names.mergeModel] as (...args: any[]) => Promise<Full>)(
802
- modelOrId,
803
- data,
804
- options,
805
- );
803
+ const updatedModel = await servedFetch<Full>(names.mergeModel)(modelOrId, data, options);
806
804
  this.set({
807
805
  [names.model]: id === model?.id ? updatedModel : model,
808
806
  [names.modelLoading]: false,
@@ -817,7 +815,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
817
815
  ) {
818
816
  const id = typeof modelOrId === "string" ? modelOrId : modelOrId.id;
819
817
  this.set({ [names.modelModal]: modal ?? "view", [names.modelLoading]: id });
820
- const model = await (fetch[names.model] as (...args: any[]) => Promise<Full>)(id, { onError });
818
+ const model = await servedFetch<Full>(names.model)(id, { onError });
821
819
  this.set({ [names.model]: model, [names.modelViewAt]: new Date(), [names.modelLoading]: false });
822
820
  },
823
821
  [names.setModel]: function (this: SetGet, ...fullOrLightModels: Full[]) {
@@ -348,6 +348,9 @@ export interface PathContextType {
348
348
  }
349
349
  export declare const pathContext: import("react").Context<PathContextType>;
350
350
  export declare const usePathCtx: () => PathContextType;
351
+ /** Inside a layout's own markup that an MCP view hides while the page is drawn on its own: frame chrome draws nothing. */
352
+ export declare const hiddenLayoutContext: import("react").Context<boolean>;
353
+ export declare const useInHiddenLayout: () => boolean;
351
354
  export type PageActivity = "current" | "prev" | "pending" | "hidden";
352
355
  export interface PageActivityState {
353
356
  activity: PageActivity;
@@ -1 +1 @@
1
- export declare const baseDictionary: import("./dictInfo.d.ts").ServiceDictInfo<[string, string], "ping" | "pingBody" | "pingParam" | "pingQuery" | "wsPing" | "pubsubPing", "serverUnreachable" | "serverUnavailable" | "gatewayTimeout" | "tooManyRequests" | "unexpectedResponse" | "immutableField" | "relationNotFound" | "idempotencyInFlight", "edit" | "error" | "ok" | "retry" | "approve" | "remove" | "skip" | "success" | "unauthorized" | "save" | "refresh" | "password" | "agent" | "stop" | "connecting" | "send" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "pageLoadFailed" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentToolCard" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentTokensOf" | "agentCompactsAt" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "view">;
1
+ export declare const baseDictionary: import("./dictInfo.d.ts").ServiceDictInfo<[string, string], "ping" | "pingBody" | "pingParam" | "pingQuery" | "wsPing" | "pubsubPing", "serverUnreachable" | "serverUnavailable" | "gatewayTimeout" | "tooManyRequests" | "unexpectedResponse" | "immutableField" | "relationNotFound" | "idempotencyInFlight" | "endpointClosed", "edit" | "error" | "ok" | "retry" | "approve" | "remove" | "skip" | "success" | "unauthorized" | "save" | "refresh" | "password" | "agent" | "stop" | "connecting" | "send" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "pageLoadFailed" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentToolCard" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentTokensOf" | "agentCompactsAt" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "view">;
@@ -1,13 +1,13 @@
1
1
  import type { AgentEndpoint, AgentTurn, BaseEndpoint } from "akanjs/signal";
2
2
  export declare const dictionary: {
3
- base: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "edit" | "error" | "ok" | "retry" | "approve" | "remove" | "skip" | "success" | "unauthorized" | "save" | "refresh" | "password" | "agent" | "stop" | "connecting" | "send" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "pageLoadFailed" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentToolCard" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentTokensOf" | "agentCompactsAt" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "view">, "base.error.gatewayTimeout" | "base.error.tooManyRequests" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.idempotencyInFlight" | "base.error.unexpectedResponse" | "base.error.immutableField" | "base.error.relationNotFound">;
3
+ base: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "edit" | "error" | "ok" | "retry" | "approve" | "remove" | "skip" | "success" | "unauthorized" | "save" | "refresh" | "password" | "agent" | "stop" | "connecting" | "send" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "pageLoadFailed" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentToolCard" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentTokensOf" | "agentCompactsAt" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "view">, "base.error.gatewayTimeout" | "base.error.tooManyRequests" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.idempotencyInFlight" | "base.error.unexpectedResponse" | "base.error.immutableField" | "base.error.relationNotFound" | "base.error.endpointClosed">;
4
4
  agentTurn: import("./locale.d.ts").DictModule<import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc`, never>;
5
5
  agent: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, "agent.error.llmUnavailable" | "agent.error.llmRequestFailed" | "agent.error.contextOverflow" | "agent.error.quotaExceeded">;
6
6
  };
7
- export declare const Err: import("./trans.d.ts").ErrConstructor<"base.error.gatewayTimeout" | "base.error.tooManyRequests" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.idempotencyInFlight" | "base.error.unexpectedResponse" | "base.error.immutableField" | "base.error.relationNotFound" | "agent.error.llmUnavailable" | "agent.error.llmRequestFailed" | "agent.error.contextOverflow" | "agent.error.quotaExceeded">, translate: (lang: "en" | "ko" | (string & {}) | "zhChs" | "zhCht" | "ja", key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "edit" | "error" | "ok" | "retry" | "approve" | "remove" | "skip" | "success" | "unauthorized" | "save" | "refresh" | "password" | "agent" | "stop" | "connecting" | "send" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "pageLoadFailed" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentToolCard" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentTokensOf" | "agentCompactsAt" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, data?: import("./trans.d.ts").TranslationData) => string, msg: {
7
+ export declare const Err: import("./trans.d.ts").ErrConstructor<"base.error.gatewayTimeout" | "base.error.tooManyRequests" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.idempotencyInFlight" | "base.error.unexpectedResponse" | "base.error.immutableField" | "base.error.relationNotFound" | "base.error.endpointClosed" | "agent.error.llmUnavailable" | "agent.error.llmRequestFailed" | "agent.error.contextOverflow" | "agent.error.quotaExceeded">, translate: (lang: "en" | "ko" | (string & {}) | "zhChs" | "zhCht" | "ja", key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "edit" | "error" | "ok" | "retry" | "approve" | "remove" | "skip" | "success" | "unauthorized" | "save" | "refresh" | "password" | "agent" | "stop" | "connecting" | "send" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "pageLoadFailed" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentToolCard" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentTokensOf" | "agentCompactsAt" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, data?: import("./trans.d.ts").TranslationData) => string, msg: {
8
8
  error: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "edit" | "error" | "ok" | "retry" | "approve" | "remove" | "skip" | "success" | "unauthorized" | "save" | "refresh" | "password" | "agent" | "stop" | "connecting" | "send" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "pageLoadFailed" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentToolCard" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentTokensOf" | "agentCompactsAt" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
9
9
  info: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "edit" | "error" | "ok" | "retry" | "approve" | "remove" | "skip" | "success" | "unauthorized" | "save" | "refresh" | "password" | "agent" | "stop" | "connecting" | "send" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "pageLoadFailed" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentToolCard" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentTokensOf" | "agentCompactsAt" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
10
10
  warning: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "edit" | "error" | "ok" | "retry" | "approve" | "remove" | "skip" | "success" | "unauthorized" | "save" | "refresh" | "password" | "agent" | "stop" | "connecting" | "send" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "pageLoadFailed" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentToolCard" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentTokensOf" | "agentCompactsAt" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
11
11
  success: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "edit" | "error" | "ok" | "retry" | "approve" | "remove" | "skip" | "success" | "unauthorized" | "save" | "refresh" | "password" | "agent" | "stop" | "connecting" | "send" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "pageLoadFailed" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentToolCard" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentTokensOf" | "agentCompactsAt" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
12
12
  loading: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "edit" | "error" | "ok" | "retry" | "approve" | "remove" | "skip" | "success" | "unauthorized" | "save" | "refresh" | "password" | "agent" | "stop" | "connecting" | "send" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "pageLoadFailed" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentToolCard" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentTokensOf" | "agentCompactsAt" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
13
- }, getDictionary: (lang: "en" | "ko" | (string & {}) | "zhChs" | "zhCht" | "ja") => object, getAllDictionary: () => import("./trans.d.ts").RootDictionary, __Dict_Key__: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "edit" | "error" | "ok" | "retry" | "approve" | "remove" | "skip" | "success" | "unauthorized" | "save" | "refresh" | "password" | "agent" | "stop" | "connecting" | "send" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "pageLoadFailed" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentToolCard" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentTokensOf" | "agentCompactsAt" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, __Error_Key__: "base.error.gatewayTimeout" | "base.error.tooManyRequests" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.idempotencyInFlight" | "base.error.unexpectedResponse" | "base.error.immutableField" | "base.error.relationNotFound" | "agent.error.llmUnavailable" | "agent.error.llmRequestFailed" | "agent.error.contextOverflow" | "agent.error.quotaExceeded";
13
+ }, getDictionary: (lang: "en" | "ko" | (string & {}) | "zhChs" | "zhCht" | "ja") => object, getAllDictionary: () => import("./trans.d.ts").RootDictionary, __Dict_Key__: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "edit" | "error" | "ok" | "retry" | "approve" | "remove" | "skip" | "success" | "unauthorized" | "save" | "refresh" | "password" | "agent" | "stop" | "connecting" | "send" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "pageLoadFailed" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentToolCard" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentTokensOf" | "agentCompactsAt" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, __Error_Key__: "base.error.gatewayTimeout" | "base.error.tooManyRequests" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.idempotencyInFlight" | "base.error.unexpectedResponse" | "base.error.immutableField" | "base.error.relationNotFound" | "base.error.endpointClosed" | "agent.error.llmUnavailable" | "agent.error.llmRequestFailed" | "agent.error.contextOverflow" | "agent.error.quotaExceeded";
@@ -10,6 +10,6 @@ export type AdaptorCls<Methods = any, InjectMap extends Record<string, InjectInf
10
10
  readonly [INJECT_META]: InjectMap;
11
11
  readonly refName: string;
12
12
  }>;
13
- export declare function adapt<Name extends string>(name: Name): AdaptorCls;
13
+ export declare function adapt<Name extends string>(name: Name): AdaptorCls<{}>;
14
14
  export declare function adapt<Name extends string, Injection extends InjectBuilder<"use" | "env" | "memory" | "plug">>(name: Name, injectBuilder: Injection): AdaptorCls<{}, ReturnType<Injection>>;
15
15
  export declare function dangerouslyAdapt<Name extends string, Injection extends InjectBuilder>(name: Name, injectBuilder: Injection): AdaptorCls<{}, ReturnType<Injection>>;
@@ -11,4 +11,4 @@ export interface BottomInsetProps {
11
11
  frameSource?: FrameSlotRegistration["source"];
12
12
  frameCache?: boolean;
13
13
  }
14
- export declare const BottomInset: ({ className, children, keyboardSticky, contentAnchor, role, estimatedHeight, frameScope, frameSource, frameCache, }: BottomInsetProps) => import("react").JSX.Element;
14
+ export declare const BottomInset: ({ className, children, keyboardSticky, contentAnchor, role, estimatedHeight, frameScope, frameSource, frameCache, }: BottomInsetProps) => import("react").JSX.Element | null;
@@ -11,4 +11,4 @@ export interface NavbarProps {
11
11
  left?: ReactNode;
12
12
  right?: ReactNode;
13
13
  }
14
- export declare const Navbar: ({ back, backFallback, className, height, children, title, left, right, }: NavbarProps) => import("react").JSX.Element;
14
+ export declare const Navbar: ({ back, backFallback, className, height, children, title, left, right, }: NavbarProps) => import("react").JSX.Element | null;
@@ -4,9 +4,9 @@ export interface TopInsetProps {
4
4
  children: ReactNode;
5
5
  estimatedHeight?: number;
6
6
  }
7
- export declare const TopInset: ({ className, children, estimatedHeight }: TopInsetProps) => import("react").JSX.Element;
7
+ export declare const TopInset: ({ className, children, estimatedHeight }: TopInsetProps) => import("react").JSX.Element | null;
8
8
  export interface TopLeftActionProps {
9
9
  className?: string;
10
10
  children: ReactNode;
11
11
  }
12
- export declare const TopLeftAction: ({ className, children }: TopLeftActionProps) => import("react").JSX.Element;
12
+ export declare const TopLeftAction: ({ className, children }: TopLeftActionProps) => import("react").JSX.Element | null;
@@ -1,15 +1,15 @@
1
1
  export declare const Layout: {
2
2
  Header: ({ className, type, children, height }: import("./Header.d.ts").HeaderProps) => import("react").JSX.Element;
3
3
  Sider: ({ className, bgClassName, trigger, header, close, children }: import("./Sider.d.ts").SiderProps) => import("react").JSX.Element;
4
- Navbar: ({ back, backFallback, className, height, children, title, left, right, }: import("./Navbar.d.ts").NavbarProps) => import("react").JSX.Element;
5
- TopInset: ({ className, children, estimatedHeight }: import("./TopInset.d.ts").TopInsetProps) => import("react").JSX.Element;
4
+ Navbar: ({ back, backFallback, className, height, children, title, left, right, }: import("./Navbar.d.ts").NavbarProps) => import("react").JSX.Element | null;
5
+ TopInset: ({ className, children, estimatedHeight }: import("./TopInset.d.ts").TopInsetProps) => import("react").JSX.Element | null;
6
6
  BottomTab: ({ className, tabs, height, label, renderTab }: import("./BottomTab.d.ts").BottomTabProps) => import("react").JSX.Element;
7
- BottomInset: ({ className, children, keyboardSticky, contentAnchor, role, estimatedHeight, frameScope, frameSource, frameCache, }: import("./BottomInset.d.ts").BottomInsetProps) => import("react").JSX.Element;
7
+ BottomInset: ({ className, children, keyboardSticky, contentAnchor, role, estimatedHeight, frameScope, frameSource, frameCache, }: import("./BottomInset.d.ts").BottomInsetProps) => import("react").JSX.Element | null;
8
8
  Template: ({ className, children }: import("./Role.d.ts").TemplateProps) => import("react").JSX.Element;
9
9
  Unit: ({ className, children, href }: import("./Role.d.ts").UnitProps) => import("react").JSX.Element;
10
10
  View: ({ className, children }: import("./Role.d.ts").ViewProps) => import("react").JSX.Element;
11
11
  Zone: ({ className, children }: import("./Role.d.ts").ZoneProps) => import("react").JSX.Element;
12
- TopLeftAction: ({ className, children }: import("./TopInset.d.ts").TopLeftActionProps) => import("react").JSX.Element;
12
+ TopLeftAction: ({ className, children }: import("./TopInset.d.ts").TopLeftActionProps) => import("react").JSX.Element | null;
13
13
  LeftSider: ({ className, children, open, width, close, onCancel }: import("./Sider.d.ts").LeftSiderProps) => import("react").JSX.Element;
14
14
  RightSider: ({ className, children, open, title, width, close, onCancel }: import("./Sider.d.ts").RightSiderProps) => import("react").JSX.Element;
15
15
  };
@@ -39,11 +39,12 @@ export interface InstallerInput {
39
39
  }
40
40
  export declare function installerScript(input: InstallerInput): string;
41
41
  /**
42
- * Stops every process running from the install folder, waits (up to 10 s) until they are gone, and exits 1 when
43
- * one is left or cannot be seen. Through WMI: the installer is a 32-bit program, so its PowerShell is too, and a
44
- * 32-bit process cannot read a 64-bit one's path (Get-Process's Path comes back empty).
42
+ * Stops every process running from the install folder, again for one started meanwhile, until none is left (up to
43
+ * 10 s); given the app's exe, deletes it in the pass that finds none, so nothing can start it again. Exits 1 when a
44
+ * process (or the exe) is left or cannot be seen. Through WMI: the installer is a 32-bit program, so its PowerShell
45
+ * is too, and a 32-bit process cannot read a 64-bit one's path (Get-Process's Path comes back empty).
45
46
  */
46
- export declare const STOP_SCRIPT = "param([string]$Dir)\n$ErrorActionPreference = 'Stop'\n$prefix = [IO.Path]::GetFullPath($Dir).TrimEnd('\\') + '\\'\n$running = { Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -and $_.ExecutablePath.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase) } }\n& $running | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }\nfor ($i = 0; $i -lt 50 -and (& $running); $i++) { Start-Sleep -Milliseconds 200 }\nif (& $running) { exit 1 }\n";
47
+ export declare const STOP_SCRIPT = "param([string]$Dir, [string]$Exe = '')\n$ErrorActionPreference = 'Stop'\n$prefix = [IO.Path]::GetFullPath($Dir).TrimEnd('\\') + '\\'\n$running = { Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -and $_.ExecutablePath.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase) } }\nfor ($i = 0; $i -lt 50; $i++) {\n $left = @(& $running)\n if ($left.Count -eq 0) {\n if (-not $Exe) { exit 0 }\n Remove-Item -LiteralPath $Exe -Force -ErrorAction SilentlyContinue\n if (-not (Test-Path -LiteralPath $Exe)) { exit 0 }\n }\n $left | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }\n Start-Sleep -Milliseconds 200\n}\nif ((& $running) -or ($Exe -and (Test-Path -LiteralPath $Exe))) { exit 1 }\n";
47
48
  /** The longest path under `folder`, relative and with Windows separators. Links are not followed. */
48
49
  export declare function deepestPath(folder: string): string;
49
50
  /** The bytes of the files under `folder`, and what they take on disk. Links are not followed. */
@@ -4,6 +4,11 @@ import type { Lifecycle } from "./lifecycle.ts";
4
4
  import type { DesktopPlugin, DesktopServerStatus, Launch, NativeEvent } from "./plugin.ts";
5
5
  /** How long the window waits for one plugin's setup. */
6
6
  export declare const SETUP_TIMEOUT_MS = 3000;
7
+ /**
8
+ * How long the launch waits for a gate plugin's setup. A gate decides whether this launch runs at all, so passing it
9
+ * by on time starts what the losing launch must not: a second app's server on the same data (single-instance).
10
+ */
11
+ export declare const GATE_TIMEOUT_MS = 30000;
7
12
  /**
8
13
  * How long a page's call to a plugin whose setup is still running (it outlasted SETUP_TIMEOUT_MS)
9
14
  * waits for it before it is answered INTERNAL with `retryable`.
@@ -90,6 +95,7 @@ export declare class CallIds {
90
95
  export declare const COALESCE_MS = 100;
91
96
  export interface DispatcherOptions {
92
97
  setupTimeoutMs?: number;
98
+ gateTimeoutMs?: number;
93
99
  setupWaitMs?: number;
94
100
  /** The app's resolved capabilities from boot.json (PL-11). Absent: everything is allowed. */
95
101
  acl?: ResolvedAcl | null;
@@ -4,7 +4,7 @@ import type { SecondInstance } from "./index.ts";
4
4
  * are one namespace for the whole PC, so the user is part of the hash there; other users cannot
5
5
  * write to a pipe they did not create (its default security), so they cannot pose as it either.
6
6
  */
7
- export declare function socketPath(appId: string, env?: Record<string, string | undefined>, platform?: NodeJS.Platform): string;
7
+ export declare function socketPath(appId: string, env?: Record<string, string | undefined>, platform?: NodeJS.Platform, profile?: string): string;
8
8
  export declare function encodeMessage(message: SecondInstance): string;
9
9
  export declare function decodeMessage(line: string): SecondInstance | null;
10
10
  export type Claim = {
@@ -1,6 +1,8 @@
1
1
  import { type ReactNode } from "react";
2
2
  export interface AgentActivityProps {
3
3
  active: boolean;
4
+ /** Guidance stays published while inactive: the subtree is off screen, but what it says about the route holds. */
5
+ keepGuides?: boolean;
4
6
  children: ReactNode;
5
7
  }
6
8
  /**
@@ -8,4 +10,4 @@ export interface AgentActivityProps {
8
10
  * it, like the page under the one on screen. Registrations stay in place, so turning it back on re-publishes them
9
11
  * without a remount. A registry that lives outside the surface reads the same gate through `useAgentGate()`.
10
12
  */
11
- export declare const AgentActivity: ({ active, children }: AgentActivityProps) => import("react").JSX.Element;
13
+ export declare const AgentActivity: ({ active, keepGuides, children }: AgentActivityProps) => import("react").JSX.Element;
@@ -38,9 +38,9 @@ export declare class AgenticSurface {
38
38
  /**
39
39
  * This surface for a subtree behind `gate`: what registers through it is published only while the gate is active,
40
40
  * and every other call reaches this surface untouched. A proxy rather than a wrapper class, so a method added to the
41
- * surface later is forwarded instead of missing.
41
+ * surface later is forwarded instead of missing. Guidance follows `guideGate` instead when one is named.
42
42
  */
43
- gated(gate: AgentGate): AgenticSurface;
43
+ gated(gate: AgentGate, guideGate?: AgentGate | null): AgenticSurface;
44
44
  snapshot(view?: string[]): SurfaceSnapshot;
45
45
  /**
46
46
  * A zone session's reading half: the same registry filtered to one scope subtree. Hook entries filter by their
@@ -0,0 +1,10 @@
1
+ import { type ReactNode } from "react";
2
+ interface EmbeddedLayoutProps {
3
+ children: ReactNode;
4
+ }
5
+ export declare const EmbeddedLayout: ({ children }: EmbeddedLayoutProps) => import("react").JSX.Element;
6
+ interface LayoutOutletProps {
7
+ children: ReactNode;
8
+ }
9
+ export declare const LayoutOutlet: ({ children }: LayoutOutletProps) => import("react").ReactPortal | null;
10
+ export {};
@@ -6,6 +6,8 @@ interface RenderLayerProps {
6
6
  params: Record<string, string>;
7
7
  searchParams: Record<string, string | string[]>;
8
8
  leaf?: ReactNode;
9
+ /** A page in an MCP view: every layout above it runs, and only the page is shown. */
10
+ isEmbedded?: boolean;
9
11
  }
10
- export declare const RenderLayer: import("react").MemoExoticComponent<({ renders, index, params, searchParams, leaf }: RenderLayerProps) => import("react").JSX.Element | null>;
12
+ export declare const RenderLayer: import("react").MemoExoticComponent<(props: RenderLayerProps) => string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined>;
11
13
  export {};
@@ -1,6 +1,13 @@
1
1
  "use client";
2
2
  import { getEnv } from "akanjs/base";
3
- import { cn, DEFAULT_BOTTOM_INSET, debugFrame, type FrameSlotRegistration, usePathCtx } from "akanjs/client";
3
+ import {
4
+ cn,
5
+ DEFAULT_BOTTOM_INSET,
6
+ debugFrame,
7
+ type FrameSlotRegistration,
8
+ useInHiddenLayout,
9
+ usePathCtx,
10
+ } from "akanjs/client";
4
11
  import { type ReactNode, useLayoutEffect, useState } from "react";
5
12
 
6
13
  import { Portal } from "../Portal";
@@ -33,6 +40,7 @@ export const BottomInset = ({
33
40
 
34
41
  const [contentElement, setContentElement] = useState<HTMLDivElement | null>(null);
35
42
  const pathCtx = usePathCtx();
43
+ const isHidden = useInHiddenLayout();
36
44
  const pathRoute = pathCtx.location?.pathRoute;
37
45
  const path = pathCtx.location?.pathRoute?.path;
38
46
  const registerFrameSlot = pathCtx.registerFrameSlot ?? (() => () => undefined);
@@ -61,7 +69,7 @@ export const BottomInset = ({
61
69
  return () => debugFrame("bottomInset.unmount", { path, frameSource });
62
70
  }, [path, keyboardSticky, contentAnchor, frameRole, frameScope, frameSource]);
63
71
  useLayoutEffect(() => {
64
- if (!path) return;
72
+ if (!path || isHidden) return;
65
73
  const height = useDeclaredHeight ? resolvedEstimatedHeight : (measuredHeight ?? readContentHeight());
66
74
  return registerFrameSlot({
67
75
  type: "bottomInset",
@@ -84,6 +92,7 @@ export const BottomInset = ({
84
92
  frameCache,
85
93
  path,
86
94
  useDeclaredHeight,
95
+ isHidden,
87
96
  ]);
88
97
  useLayoutEffect(() => {
89
98
  const element = contentElement;
@@ -102,6 +111,7 @@ export const BottomInset = ({
102
111
  observer.observe(element);
103
112
  return () => observer.disconnect();
104
113
  }, [contentElement]);
114
+ if (isHidden) return null;
105
115
 
106
116
  const content = (
107
117
  <div
@@ -1,6 +1,14 @@
1
1
  "use client";
2
2
  import { getEnv } from "akanjs/base";
3
- import { cn, DEFAULT_TOP_INSET, debugFrame, type TransitionType, usePathCtx } from "akanjs/client";
3
+ import {
4
+ cn,
5
+ DEFAULT_TOP_INSET,
6
+ debugFrame,
7
+ type TransitionType,
8
+ useCsr,
9
+ useInHiddenLayout,
10
+ usePathCtx,
11
+ } from "akanjs/client";
4
12
  import { type ReactNode, useLayoutEffect } from "react";
5
13
  import { BiChevronLeft, BiX } from "react-icons/bi";
6
14
 
@@ -34,8 +42,14 @@ export const Navbar = ({
34
42
  right,
35
43
  }: NavbarProps) => {
36
44
  const pathCtx = usePathCtx();
45
+ const { history } = useCsr();
46
+ const isHidden = useInHiddenLayout();
37
47
  const pathRoute = pathCtx.location?.pathRoute;
38
48
  const path = pathRoute?.path;
49
+ const entryId = pathCtx.location?.entryId;
50
+
51
+ const isViewStart = !!globalThis.__AKAN_EMBED__ && !!entryId && history.current?.locations[0]?.entryId === entryId;
52
+ const shownBack = isViewStart ? false : back;
39
53
  const BackIcon = closeIconTransitions.has(pathRoute?.pageState.transition ?? "none") ? BiX : BiChevronLeft;
40
54
  const registerFrameSlot = pathCtx.registerFrameSlot ?? (() => () => undefined);
41
55
  const suffix = getEnv().renderMode === "csr" && path ? `-${pathCtx.pageKey ?? path}` : "";
@@ -45,7 +59,7 @@ export const Navbar = ({
45
59
  return () => debugFrame("navbar.unmount", { path, height });
46
60
  }, [path, height]);
47
61
  useLayoutEffect(() => {
48
- if (!path) return;
62
+ if (!path || isHidden) return;
49
63
  return registerFrameSlot({
50
64
  type: "topInset",
51
65
  scope: "page",
@@ -53,9 +67,10 @@ export const Navbar = ({
53
67
  estimatedHeight: height ?? DEFAULT_TOP_INSET,
54
68
  height,
55
69
  });
56
- }, [registerFrameSlot, height, path]);
70
+ }, [registerFrameSlot, height, path, isHidden]);
71
+ if (isHidden) return null;
57
72
  const bar = (
58
- <div className={cn("flex size-full items-center gap-2 px-5", back && "pl-14", className)}>
73
+ <div className={cn("flex size-full items-center gap-2 px-5", shownBack && "pl-14", className)}>
59
74
  {left ? <div className="flex shrink-0 items-center gap-1">{left}</div> : null}
60
75
  {title ? <div className="min-w-0 flex-1 truncate font-semibold text-base">{title}</div> : null}
61
76
  {children}
@@ -63,19 +78,19 @@ export const Navbar = ({
63
78
  </div>
64
79
  );
65
80
  const backButton =
66
- back === true ? (
81
+ shownBack === true ? (
67
82
  <Link.Back className="text-4xl" fallback={backFallback}>
68
83
  <BackIcon />
69
84
  </Link.Back>
70
85
  ) : (
71
- back
86
+ shownBack
72
87
  );
73
88
  return (
74
89
  <>
75
90
  <Portal id={`topInsetContent${suffix}`} fallback={<FrameSlotFallback slot="topInset">{bar}</FrameSlotFallback>}>
76
91
  {bar}
77
92
  </Portal>
78
- {back ? (
93
+ {shownBack ? (
79
94
  <Portal
80
95
  id={`topLeftActionContent${suffix}`}
81
96
  fallback={<FrameSlotFallback slot="topLeftAction">{backButton}</FrameSlotFallback>}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { getEnv } from "akanjs/base";
3
- import { cn, DEFAULT_TOP_INSET, debugFrame, usePathCtx } from "akanjs/client";
3
+ import { cn, DEFAULT_TOP_INSET, debugFrame, useInHiddenLayout, usePathCtx } from "akanjs/client";
4
4
  import { type ReactNode, useLayoutEffect } from "react";
5
5
 
6
6
  import { Portal } from "../Portal";
@@ -14,6 +14,7 @@ export interface TopInsetProps {
14
14
 
15
15
  export const TopInset = ({ className, children, estimatedHeight = DEFAULT_TOP_INSET }: TopInsetProps) => {
16
16
  const pathCtx = usePathCtx();
17
+ const isHidden = useInHiddenLayout();
17
18
  const path = pathCtx.location?.pathRoute?.path;
18
19
  const registerFrameSlot = pathCtx.registerFrameSlot ?? (() => () => undefined);
19
20
  const suffix = getEnv().renderMode === "csr" && path ? `-${pathCtx.pageKey ?? path}` : "";
@@ -24,14 +25,15 @@ export const TopInset = ({ className, children, estimatedHeight = DEFAULT_TOP_IN
24
25
  return () => debugFrame("topInset.unmount", { path, estimatedHeight });
25
26
  }, [path, estimatedHeight]);
26
27
  useLayoutEffect(() => {
27
- if (!path) return;
28
+ if (!path || isHidden) return;
28
29
  return registerFrameSlot({
29
30
  type: "topInset",
30
31
  scope: "page",
31
32
  source: "topInset",
32
33
  estimatedHeight,
33
34
  });
34
- }, [registerFrameSlot, estimatedHeight, path]);
35
+ }, [registerFrameSlot, estimatedHeight, path, isHidden]);
36
+ if (isHidden) return null;
35
37
 
36
38
  const content = (
37
39
  <div data-akan-frame-slot="topInset" data-akan-frame-role="topChrome" className={cn("size-full", className)}>
@@ -52,6 +54,8 @@ export interface TopLeftActionProps {
52
54
 
53
55
  export const TopLeftAction = ({ className, children }: TopLeftActionProps) => {
54
56
  const pathCtx = usePathCtx();
57
+ const isHidden = useInHiddenLayout();
58
+ if (isHidden) return null;
55
59
  const path = pathCtx.location?.pathRoute?.path;
56
60
  const suffix = getEnv().renderMode === "csr" && path ? `-${pathCtx.pageKey ?? path}` : "";
57
61
  const content = <div className={className}>{children}</div>;
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { usePage } from "akanjs/client";
2
+ import { fetch, usePage } from "akanjs/client";
3
3
  import { capitalize } from "akanjs/common";
4
4
  import type { SliceMeta } from "akanjs/fetch";
5
5
  import { st } from "akanjs/store";
@@ -110,6 +110,10 @@ export default function ViewEditModal({
110
110
  const modelLoading = storeUse[names.modelLoading]() as string | boolean;
111
111
  const modelFormLoading = storeUse[names.modelFormLoading]() as string | boolean;
112
112
  const modelFormId = storeSel<string, { id: string }>((state) => state[names.modelForm].id);
113
+
114
+ const servedFetch = fetch as unknown as { [key: string]: unknown };
115
+ const canUpdate = typeof servedFetch[`update${ModelName}`] === "function";
116
+ const canRemove = typeof servedFetch[`remove${ModelName}`] === "function";
113
117
  const isModalOpen = modelModal === "view" || (modelModal === "edit" && (!!modelFormLoading || !!modelFormId));
114
118
  const Title = () => {
115
119
  if (!model || modelLoading || !renderTitle) return <></>;
@@ -133,7 +137,7 @@ export default function ViewEditModal({
133
137
  title={
134
138
  <div className="flex w-full items-center justify-between">
135
139
  <Title />
136
- {menu === false
140
+ {menu === false || (menu === undefined && !canRemove)
137
141
  ? null
138
142
  : (menu ?? (
139
143
  <Dropdown
@@ -159,14 +163,16 @@ export default function ViewEditModal({
159
163
  }
160
164
  action={
161
165
  modelModal === "view" ? (
162
- <EditAction
163
- modelName={modelName}
164
- label={editLabel ?? l("base.edit")}
165
- closeView={closeView}
166
- onAct={() => {
167
- if (model) storeDo[names.editModel](model.id);
168
- }}
169
- />
166
+ canUpdate ? (
167
+ <EditAction
168
+ modelName={modelName}
169
+ label={editLabel ?? l("base.edit")}
170
+ closeView={closeView}
171
+ onAct={() => {
172
+ if (model) storeDo[names.editModel](model.id);
173
+ }}
174
+ />
175
+ ) : null
170
176
  ) : (
171
177
  <SaveAction
172
178
  modelName={modelName}
package/ui/Select.tsx CHANGED
@@ -95,14 +95,19 @@ const DefaultSelect = <
95
95
  renderSelected,
96
96
  }: SelectProps<T, Multiple, Searchable, Option>) => {
97
97
  useFieldTool(onChange, { disabled });
98
- const { l } = usePage();
98
+ const { l, lang } = usePage();
99
99
  const [isOpen, setIsOpen] = useState(false);
100
100
  const labeledOptions: LabelOption<T>[] = useMemo(() => {
101
- if (isEnum(options as Cls))
102
- return (options as EnumInstance<string, T>).values.map((v) => ({
103
- label: typeof v === "string" ? v : typeof v === "object" ? JSON.stringify(v) : String(v),
104
- value: v,
105
- }));
101
+ if (isEnum(options as Cls)) {
102
+ const { refName, values } = options as EnumInstance<string, T>;
103
+ return values.map((v) => {
104
+ if (typeof v === "object") return { label: JSON.stringify(v), value: v };
105
+ const key = `${refName}.${String(v)}`;
106
+ const label = l._(key);
107
+
108
+ return { label: label === key ? String(v) : label, value: v };
109
+ });
110
+ }
106
111
  const list = options as (T | LabelOption<T>)[];
107
112
  const first = list[0];
108
113
 
@@ -110,7 +115,7 @@ const DefaultSelect = <
110
115
  return isLabeled
111
116
  ? (list as LabelOption<T>[])
112
117
  : (list as T[]).map((v) => ({ label: v as string | boolean | number, value: v }));
113
- }, [options]);
118
+ }, [options, lang]);
114
119
 
115
120
  const [searchText, setSearchText] = useState("");
116
121
  const portal = useOverlayPortal();
package/ui/System/CSR.tsx CHANGED
@@ -483,7 +483,13 @@ const CSRPageContainer = ({ entry, prefix, layoutStyle }: CSRPageContainerProps)
483
483
  location={location}
484
484
  prefix={prefix}
485
485
  >
486
- <RenderLayer renders={renders} index={0} params={location.params} searchParams={location.searchParams} />
486
+ <RenderLayer
487
+ renders={renders}
488
+ index={0}
489
+ params={location.params}
490
+ searchParams={location.searchParams}
491
+ isEmbedded={Boolean(globalThis.__AKAN_EMBED__)}
492
+ />
487
493
  </ClientPathWrapper>
488
494
  </animated.div>,
489
495
  pageContainers,
@@ -189,6 +189,7 @@ export const ClientPathWrapper = ({
189
189
  searchParams,
190
190
  search,
191
191
  pathRoute,
192
+ ...(location?.entryId ? { entryId: location.entryId } : {}),
192
193
  },
193
194
  prefix,
194
195
  gestureEnabled,
@@ -87,6 +87,9 @@ ManifestDPIAware true
87
87
  ManifestLongPathAware true
88
88
  SetCompressor /SOLID lzma
89
89
  RequestExecutionLevel user
90
+ ; /S does not hide NSIS's "Verifying installer" window (only a script's SilentInstall does), and a large setup takes
91
+ ; over a second to verify, so the window painted over a kiosk. A truncated setup still fails to unpack into .setup-new.
92
+ CRCCheck off
90
93
 
91
94
  !include "MUI2.nsh"
92
95
  !include "FileFunc.nsh"
@@ -188,8 +191,9 @@ FunctionEnd
188
191
  ${removal(input.entries)}
189
192
  !macroend
190
193
 
194
+ ; $R6 is 1 when the folder held the app's exe before the uninstall stopped the app and deleted it.
191
195
  !macro RemoveApp
192
- \${If} \${FileExists} "$INSTDIR\\\${EXE}"
196
+ \${If} $R6 == 1
193
197
  !insertmacro RemoveTree "$INSTDIR"
194
198
  \${Else}
195
199
  !insertmacro RemoveEntries
@@ -358,7 +362,9 @@ Section "Install"
358
362
  !insertmacro Fail "The uninstaller could not be written to $INSTDIR.setup-uninstall.exe. \${APP_NAME} is left as it was."
359
363
  \${EndIf}
360
364
  File "/oname=$PLUGINSDIR\\stop-app.ps1" "${nsis(input.stopScript)}"
361
- nsExec::Exec 'powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$PLUGINSDIR\\stop-app.ps1" "$INSTDIR"'
365
+ ; nsExec waits forever by default: a stop script that hung once kept a setup waiting 16 minutes. Past the deadline it
366
+ ; is ended and answers "timeout", which counts as a failed stop.
367
+ nsExec::Exec /TIMEOUT=60000 'powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$PLUGINSDIR\\stop-app.ps1" "$INSTDIR"'
362
368
  Pop $0
363
369
  \${If} $0 == 0
364
370
  StrCpy $AppStopped 1
@@ -438,9 +444,19 @@ FunctionEnd
438
444
 
439
445
  Section "Uninstall"
440
446
  InitPluginsDir
447
+ StrCpy $R6 0
448
+ \${If} \${FileExists} "$INSTDIR\\\${EXE}"
449
+ StrCpy $R6 1
450
+ \${EndIf}
441
451
  File "/oname=$PLUGINSDIR\\stop-app.ps1" "${nsis(input.stopScript)}"
442
- nsExec::Exec 'powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$PLUGINSDIR\\stop-app.ps1" "$INSTDIR"'
452
+ ; With the exe named, the script deletes it in the pass that finds nothing running: a watchdog that starts the app
453
+ ; again between a stop and a later delete kept the exe, and Delete /REBOOTOK needs an administrator.
454
+ nsExec::Exec /TIMEOUT=60000 'powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$PLUGINSDIR\\stop-app.ps1" "$INSTDIR" "$INSTDIR\\\${EXE}"'
443
455
  Pop $0
456
+ \${If} $0 != 0
457
+ DetailPrint "$INSTDIR\\\${EXE} is still running and stays behind."
458
+ SetErrorLevel 2
459
+ \${EndIf}
444
460
  Delete "$SMPROGRAMS\\\${APP_NAME}.lnk"
445
461
  !insertmacro RemoveTree "$INSTDIR.previous"
446
462
  !insertmacro RemoveSiblings "update-"
@@ -465,17 +481,26 @@ SectionEnd
465
481
  }
466
482
 
467
483
  /**
468
- * Stops every process running from the install folder, waits (up to 10 s) until they are gone, and exits 1 when
469
- * one is left or cannot be seen. Through WMI: the installer is a 32-bit program, so its PowerShell is too, and a
470
- * 32-bit process cannot read a 64-bit one's path (Get-Process's Path comes back empty).
484
+ * Stops every process running from the install folder, again for one started meanwhile, until none is left (up to
485
+ * 10 s); given the app's exe, deletes it in the pass that finds none, so nothing can start it again. Exits 1 when a
486
+ * process (or the exe) is left or cannot be seen. Through WMI: the installer is a 32-bit program, so its PowerShell
487
+ * is too, and a 32-bit process cannot read a 64-bit one's path (Get-Process's Path comes back empty).
471
488
  */
472
- export const STOP_SCRIPT = `param([string]$Dir)
489
+ export const STOP_SCRIPT = `param([string]$Dir, [string]$Exe = '')
473
490
  $ErrorActionPreference = 'Stop'
474
491
  $prefix = [IO.Path]::GetFullPath($Dir).TrimEnd('\\') + '\\'
475
492
  $running = { Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -and $_.ExecutablePath.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase) } }
476
- & $running | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
477
- for ($i = 0; $i -lt 50 -and (& $running); $i++) { Start-Sleep -Milliseconds 200 }
478
- if (& $running) { exit 1 }
493
+ for ($i = 0; $i -lt 50; $i++) {
494
+ $left = @(& $running)
495
+ if ($left.Count -eq 0) {
496
+ if (-not $Exe) { exit 0 }
497
+ Remove-Item -LiteralPath $Exe -Force -ErrorAction SilentlyContinue
498
+ if (-not (Test-Path -LiteralPath $Exe)) { exit 0 }
499
+ }
500
+ $left | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
501
+ Start-Sleep -Milliseconds 200
502
+ }
503
+ if ((& $running) -or ($Exe -and (Test-Path -LiteralPath $Exe))) { exit 1 }
479
504
  `;
480
505
 
481
506
  /** The longest path under `folder`, relative and with Windows separators. Links are not followed. */
@@ -33,6 +33,12 @@ import type {
33
33
  /** How long the window waits for one plugin's setup. */
34
34
  export const SETUP_TIMEOUT_MS = 3000;
35
35
 
36
+ /**
37
+ * How long the launch waits for a gate plugin's setup. A gate decides whether this launch runs at all, so passing it
38
+ * by on time starts what the losing launch must not: a second app's server on the same data (single-instance).
39
+ */
40
+ export const GATE_TIMEOUT_MS = 30_000;
41
+
36
42
  /**
37
43
  * How long a page's call to a plugin whose setup is still running (it outlasted SETUP_TIMEOUT_MS)
38
44
  * waits for it before it is answered INTERNAL with `retryable`.
@@ -189,6 +195,7 @@ export const COALESCE_MS = 100;
189
195
 
190
196
  export interface DispatcherOptions {
191
197
  setupTimeoutMs?: number;
198
+ gateTimeoutMs?: number;
192
199
  setupWaitMs?: number;
193
200
  /** The app's resolved capabilities from boot.json (PL-11). Absent: everything is allowed. */
194
201
  acl?: ResolvedAcl | null;
@@ -301,18 +308,19 @@ export function createDispatcher(
301
308
  };
302
309
 
303
310
  const timeout = options.setupTimeoutMs ?? SETUP_TIMEOUT_MS;
311
+ const gateTimeout = options.gateTimeoutMs ?? GATE_TIMEOUT_MS;
304
312
  /** Setups still running (plugin lifecycle rule: calls and $listen wait for them). */
305
313
  const settingUp = new Map<string, Promise<unknown>>();
306
- const setUp = async (plugin: DesktopPlugin) => {
314
+ const setUp = async (plugin: DesktopPlugin, limit = timeout) => {
307
315
  let timer: ReturnType<typeof setTimeout> | undefined;
308
316
  try {
309
317
  const done = Promise.resolve(plugin.setup?.(context(plugin)));
310
318
  settingUp.set(plugin.id, done);
311
319
  const forget = () => void settingUp.delete(plugin.id);
312
320
  done.then(forget, forget);
313
- const late = new Promise<"late">((resolve) => (timer = setTimeout(() => resolve("late"), timeout)));
321
+ const late = new Promise<"late">((resolve) => (timer = setTimeout(() => resolve("late"), limit)));
314
322
  if ((await Promise.race([done, late])) === "late")
315
- console.warn(`[akan-native] ${plugin.id} setup took over ${timeout} ms; creating the window anyway`);
323
+ console.warn(`[akan-native] ${plugin.id} setup took over ${limit} ms; creating the window anyway`);
316
324
  } catch (error) {
317
325
  console.error(`[akan-native] ${plugin.id} setup failed`, error);
318
326
  } finally {
@@ -322,10 +330,10 @@ export function createDispatcher(
322
330
 
323
331
  const setups = (async () => {
324
332
  for (const plugin of plugins.filter((p) => p.launchPhase === "gate")) {
325
- await setUp(plugin);
333
+ await setUp(plugin, gateTimeout);
326
334
  if (launch.exit !== undefined) return;
327
335
  }
328
- await Promise.all(plugins.filter((p) => p.launchPhase !== "gate").map(setUp));
336
+ await Promise.all(plugins.filter((p) => p.launchPhase !== "gate").map((plugin) => setUp(plugin)));
329
337
  })();
330
338
  const launched = setups.then(() => {
331
339
  launching = false;
@@ -20,6 +20,10 @@ export function createDesktopSingleInstance(
20
20
  let listens = 0;
21
21
 
22
22
  const received = (ctx: DesktopContext, message: SecondInstance) => {
23
+
24
+ console.info(
25
+ `[akan-native] single-instance: a second launch handed over to this one (${message.args.length} arguments)`,
26
+ );
23
27
  bringToFront(ctx).catch((error) => console.error("[akan-native] single-instance: cannot focus the window", error));
24
28
 
25
29
  ctx.openLinks(message.args);
@@ -4,7 +4,7 @@ import { dlopen, FFIType } from "bun:ffi";
4
4
  import { createHash } from "node:crypto";
5
5
  import { chmodSync, closeSync, constants, openSync, rmSync } from "node:fs";
6
6
  import { connect, createServer, type Server, type Socket } from "node:net";
7
- import { tmpdir } from "node:os";
7
+ import { tmpdir, userInfo } from "node:os";
8
8
  import { join } from "node:path";
9
9
  import type { SecondInstance } from "./index.ts";
10
10
 
@@ -13,6 +13,14 @@ const ACK = "ok\n";
13
13
  /** One message is a few KiB of arguments; anything larger is not from us. */
14
14
  const MAX_MESSAGE = 256 * 1024;
15
15
 
16
+ const profileFolder = (env: Record<string, string | undefined>) => {
17
+ try {
18
+ return userInfo().homedir;
19
+ } catch {
20
+ return env.USERPROFILE ?? env.USERNAME ?? "";
21
+ }
22
+ };
23
+
16
24
  /**
17
25
  * sun_path is 104 bytes on macOS, so the name is a short hash, not the app id. Windows pipe names
18
26
  * are one namespace for the whole PC, so the user is part of the hash there; other users cannot
@@ -22,12 +30,11 @@ export function socketPath(
22
30
  appId: string,
23
31
  env: Record<string, string | undefined> = process.env,
24
32
  platform: NodeJS.Platform = process.platform,
33
+ profile = platform === "win32" ? profileFolder(env) : "",
25
34
  ): string {
26
35
  if (platform === "win32") {
27
- const hash = createHash("sha256")
28
- .update(`${env.USERDOMAIN ?? ""}\\${env.USERNAME ?? ""}\0${appId}`)
29
- .digest("hex")
30
- .slice(0, 16);
36
+
37
+ const hash = createHash("sha256").update(`${profile.toLowerCase()}\0${appId}`).digest("hex").slice(0, 16);
31
38
  return `\\\\.\\pipe\\akan-native-${hash}`;
32
39
  }
33
40
  const dir = env.XDG_RUNTIME_DIR || env.TMPDIR || tmpdir();
@@ -4,6 +4,8 @@ import { GateContext, SurfaceContext, useAgentGate, useSurface } from "./surface
4
4
 
5
5
  export interface AgentActivityProps {
6
6
  active: boolean;
7
+ /** Guidance stays published while inactive: the subtree is off screen, but what it says about the route holds. */
8
+ keepGuides?: boolean;
7
9
  children: ReactNode;
8
10
  }
9
11
 
@@ -12,11 +14,11 @@ export interface AgentActivityProps {
12
14
  * it, like the page under the one on screen. Registrations stay in place, so turning it back on re-publishes them
13
15
  * without a remount. A registry that lives outside the surface reads the same gate through `useAgentGate()`.
14
16
  */
15
- export const AgentActivity = ({ active, children }: AgentActivityProps) => {
17
+ export const AgentActivity = ({ active, keepGuides = false, children }: AgentActivityProps) => {
16
18
  const surface = useSurface();
17
19
  const parent = useAgentGate();
18
20
  const gate = useMemo(() => surface.gate(active, parent), [surface, parent]);
19
- const gated = useMemo(() => surface.gated(gate), [surface, gate]);
21
+ const gated = useMemo(() => surface.gated(gate, keepGuides ? parent : gate), [surface, gate, parent, keepGuides]);
20
22
  useLayoutEffect(() => gate.set(active), [gate, active]);
21
23
  return (
22
24
  <SurfaceContext.Provider value={gated}>
@@ -127,14 +127,14 @@ export class AgenticSurface {
127
127
  /**
128
128
  * This surface for a subtree behind `gate`: what registers through it is published only while the gate is active,
129
129
  * and every other call reaches this surface untouched. A proxy rather than a wrapper class, so a method added to the
130
- * surface later is forwarded instead of missing.
130
+ * surface later is forwarded instead of missing. Guidance follows `guideGate` instead when one is named.
131
131
  */
132
- gated(gate: AgentGate): AgenticSurface {
132
+ gated(gate: AgentGate, guideGate: AgentGate | null = gate): AgenticSurface {
133
133
  const registrations: Partial<Record<PropertyKey, unknown>> = {
134
134
  registerTool: (scope: string[], entry: ToolEntry) => this.registerTool(scope, entry, gate),
135
135
  registerResource: (scope: string[], entry: ResourceEntry) => this.registerResource(scope, entry, gate),
136
136
  openScope: (parent: string[], scope: ScopeEntry) => this.openScope(parent, scope, gate),
137
- registerGuide: (scope: string[], text: string) => this.registerGuide(scope, text, gate),
137
+ registerGuide: (scope: string[], text: string) => this.registerGuide(scope, text, guideGate),
138
138
  };
139
139
 
140
140
  const bound = new Map<PropertyKey, unknown>();
@@ -190,7 +190,8 @@ export class CsrRouteTable {
190
190
  ): PathRoute[] => {
191
191
  const layer = RouteLayering.of(route, parent);
192
192
  const { path, pathSegments } = layer;
193
- const currentLayoutConfig = route.renderLayout && route.layoutPageConfig ? route.layoutPageConfig : null;
193
+ const currentLayoutConfig =
194
+ route.renderLayout && route.layoutPageConfig ? CsrRouteTable.#layoutConfigOf(route.layoutPageConfig) : null;
194
195
  const pageConfigChain = [...parentPageConfigChain, ...(currentLayoutConfig ? [currentLayoutConfig] : [])];
195
196
  const pageRenderConfigChain =
196
197
  route.pageIncludesOwnLayout === false && route.renderLayout ? parentPageConfigChain : pageConfigChain;
@@ -250,6 +251,12 @@ export class CsrRouteTable {
250
251
  return Object.keys(context).sort();
251
252
  }
252
253
 
254
+ static #layoutConfigOf(config: PageConfig): PageConfig {
255
+ if (!globalThis.__AKAN_EMBED__) return config;
256
+ const { topInset: _top, bottomInset: _bottom, ...rest } = config;
257
+ return rest;
258
+ }
259
+
253
260
  static #paramNamesOf(pattern: string) {
254
261
  return pattern
255
262
  .split("/")
@@ -0,0 +1,77 @@
1
+ "use client";
2
+ import { hiddenLayoutContext } from "akanjs/client";
3
+ import {
4
+ type ContextType,
5
+ createContext,
6
+ type ReactNode,
7
+ useCallback,
8
+ useContext,
9
+ useLayoutEffect,
10
+ useMemo,
11
+ useRef,
12
+ useState,
13
+ } from "react";
14
+ import { createPortal } from "react-dom";
15
+ import { AgentActivity, GateContext, SurfaceContext } from "../vendor/use-agentic";
16
+
17
+ interface EmbeddedLayoutValue {
18
+ target: HTMLElement | null;
19
+ onOutlet: (mounted: boolean) => void;
20
+ surface: ContextType<typeof SurfaceContext>;
21
+ gate: ContextType<typeof GateContext>;
22
+ }
23
+ const embeddedLayoutContext = createContext<EmbeddedLayoutValue | null>(null);
24
+
25
+ interface EmbeddedLayoutProps {
26
+ children: ReactNode;
27
+ }
28
+ export const EmbeddedLayout = ({ children }: EmbeddedLayoutProps) => {
29
+ const surface = useContext(SurfaceContext);
30
+ const gate = useContext(GateContext);
31
+ const [target, setTarget] = useState<HTMLDivElement | null>(null);
32
+ const [drawsPage, setDrawsPage] = useState(true);
33
+ const outletRef = useRef(false);
34
+ const onOutlet = useCallback((mounted: boolean) => {
35
+ outletRef.current = mounted;
36
+ setDrawsPage(mounted);
37
+ }, []);
38
+
39
+ useLayoutEffect(() => {
40
+ if (!outletRef.current) setDrawsPage(false);
41
+ });
42
+ const value = useMemo(() => ({ target, onOutlet, surface, gate }), [target, onOutlet, surface, gate]);
43
+ return (
44
+ <embeddedLayoutContext.Provider value={value}>
45
+ <div hidden={drawsPage}>
46
+ <hiddenLayoutContext.Provider value={drawsPage}>
47
+ <AgentActivity active={!drawsPage} keepGuides>
48
+ {children}
49
+ </AgentActivity>
50
+ </hiddenLayoutContext.Provider>
51
+ </div>
52
+ <div ref={setTarget} className="contents" />
53
+ </embeddedLayoutContext.Provider>
54
+ );
55
+ };
56
+
57
+ interface LayoutOutletProps {
58
+ children: ReactNode;
59
+ }
60
+ export const LayoutOutlet = ({ children }: LayoutOutletProps) => {
61
+ const embedded = useContext(embeddedLayoutContext);
62
+ const onOutlet = embedded?.onOutlet;
63
+ useLayoutEffect(() => {
64
+ if (!onOutlet) return;
65
+ onOutlet(true);
66
+ return () => onOutlet(false);
67
+ }, [onOutlet]);
68
+ if (!embedded?.target) return null;
69
+ return createPortal(
70
+ <SurfaceContext.Provider value={embedded.surface}>
71
+ <GateContext.Provider value={embedded.gate}>
72
+ <hiddenLayoutContext.Provider value={false}>{children}</hiddenLayoutContext.Provider>
73
+ </GateContext.Provider>
74
+ </SurfaceContext.Provider>,
75
+ embedded.target,
76
+ );
77
+ };
@@ -3,6 +3,7 @@ import { debugFrame, RouteDefinition, type RouteRender, router, usePathCtx } fro
3
3
  import { isThenable, Logger } from "akanjs/common";
4
4
  import { createContext, createElement, memo, type ReactNode, useContext, useEffect, useRef, useState } from "react";
5
5
  import { CsrFrameDump } from "./csrFrameDump";
6
+ import { EmbeddedLayout, LayoutOutlet } from "./EmbeddedLayout";
6
7
  import { RenderFailure } from "./RenderFailure";
7
8
  import { useFetch } from "./useFetch";
8
9
  import { usePageActivity } from "./usePageActivity";
@@ -28,15 +29,28 @@ interface RenderLayerProps {
28
29
  params: Record<string, string>;
29
30
  searchParams: Record<string, string | string[]>;
30
31
  leaf?: ReactNode;
32
+ /** A page in an MCP view: every layout above it runs, and only the page is shown. */
33
+ isEmbedded?: boolean;
31
34
  }
32
- export const RenderLayer = memo(({ renders, index, params, searchParams, leaf = null }: RenderLayerProps) => {
35
+ export const RenderLayer = memo((props: RenderLayerProps) => {
36
+ const { renders, index, params, searchParams, leaf = null, isEmbedded = false } = props;
33
37
  const isLast = index >= renders.length - 1;
34
- const children = isLast ? (
38
+ const below = isLast ? (
35
39
  leaf
36
40
  ) : (
37
- <RenderLayer renders={renders} index={index + 1} params={params} searchParams={searchParams} leaf={leaf} />
41
+ <RenderLayer
42
+ renders={renders}
43
+ index={index + 1}
44
+ params={params}
45
+ searchParams={searchParams}
46
+ leaf={leaf}
47
+ isEmbedded={isEmbedded}
48
+ />
38
49
  );
39
50
  const routeRender = renders[index];
51
+ const isEmbeddedLayout = isEmbedded && routeRender?.kind === "layout";
52
+ const children = isEmbeddedLayout ? <LayoutOutlet>{below}</LayoutOutlet> : below;
53
+ const frame = (drawn: ReactNode) => (isEmbeddedLayout ? <EmbeddedLayout>{drawn}</EmbeddedLayout> : drawn);
40
54
  const isAsyncRender = isAsyncRouteRender(routeRender);
41
55
  const renderedRef = useRef<RenderedRoute | null>(null);
42
56
  const [, setFailures] = useState(0);
@@ -106,7 +120,8 @@ export const RenderLayer = memo(({ renders, index, params, searchParams, leaf =
106
120
  };
107
121
  }, [record]);
108
122
  if (!routeRender) return null;
109
- if (!isAsyncRender) return createElement(routeRender.render as never, { children, params, searchParams } as never);
123
+ if (!isAsyncRender)
124
+ return frame(createElement(routeRender.render as never, { children, params, searchParams } as never));
110
125
  const failure = renderedRef.current?.failure;
111
126
  if (failure)
112
127
  return (
@@ -119,7 +134,7 @@ export const RenderLayer = memo(({ renders, index, params, searchParams, leaf =
119
134
  />
120
135
  );
121
136
  if (!value) return <>{composeLoadingFallback(renders.slice(index), params)}</>;
122
- return <layerChildContext.Provider value={children}>{value}</layerChildContext.Provider>;
137
+ return frame(<layerChildContext.Provider value={children}>{value}</layerChildContext.Provider>);
123
138
  });
124
139
 
125
140
  function reportFailure(result: ReactNode | Promise<ReactNode>, layer: string): ReactNode | Promise<ReactNode> {