diff --git a/README.md b/README.md index 5043395..ab17df6 100644 --- a/README.md +++ b/README.md @@ -297,3 +297,6 @@ Use `bash scripts/start.sh` for the whole stack. Use `bun run dev` only when you ## License [MIT](./LICENSE) — upstream copyright remains with CopilotKit; this product fork is maintained by Kayco. + +Some adapted UI components retain their original Apache-2.0 terms. See +[THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) for source and license details. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..75d94f5 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,23 @@ +# Third-party notices + +## OpenMausBot UI components + +Portions of Kayco OpenBot's user interface are adapted from +[OpenMausBot](https://github.com/milind-soni/OpenMausBot) commit +`ca3131444bbb2b125e8d90593be930efb2f19854`. + +Copyright 2026 Milind Soni and OpenMausBot contributors. + +OpenMausBot is licensed under the Apache License, Version 2.0. A copy is +included at [licenses/OpenMausBot-APACHE-2.0.txt](licenses/OpenMausBot-APACHE-2.0.txt). + +Adapted surfaces: + +- `app/src/components/app-sidebar/channel.tsx`: contact-style conversation row and revealed actions. +- `app/src/components/channels/codex-status.tsx`: compact model trigger and grouped model menu. +- `app/src/components/gallery/decisions.tsx`: lettered option card and free-text answer. +- `app/src/routes/_authed/admin/plugins.tsx`: searchable two-column connected-app catalogue. + +These files carry source notices and were modified to use Kayco's routing, +design tokens, server-owned permissions, credential vault, approval flow, and +Codex preference APIs. diff --git a/app/src/components/app-sidebar/app-sidebar.tsx b/app/src/components/app-sidebar/app-sidebar.tsx index 35cc645..fd9d11f 100644 --- a/app/src/components/app-sidebar/app-sidebar.tsx +++ b/app/src/components/app-sidebar/app-sidebar.tsx @@ -1,12 +1,13 @@ import { IconBolt, + IconBox, IconBriefcase, IconLogout, + IconPlugConnected, IconPlus, IconSearch, IconSettings, IconShieldLock, - IconBox, } from "@tabler/icons-react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Link, type LinkOptions, useNavigate } from "@tanstack/react-router"; @@ -29,6 +30,7 @@ import { SidebarContent, SidebarFooter, SidebarGroup, + SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, @@ -43,8 +45,8 @@ import { } from "@/lib/channels/queries"; import { useChannelEvents } from "@/lib/channels/use-channel-events"; import { appConfig } from "@/lib/generated/application-config"; -import { Button } from "../ui/button"; import { CommandPalette } from "../command-palette"; +import { Button } from "../ui/button"; import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "../ui/empty"; import { Channel } from "./channel"; @@ -176,7 +178,7 @@ export function AppSidebar({ ...props }: React.ComponentProps) { return ( - + ) { )} /> + + + } + > + + {selectedModel} + {selected.effort ? ( + + · {effortLabel(selected.effort)} + + ) : null} + + + +
+

Choose a model

+

+ Applies to built-in coworkers that use this ChatGPT account. +

+
+ + savePreference({ model: value === "__default__" ? null : value }) + } + value={selected.model ?? "__default__"} + > + Model + + Deployment default + + {modelChoices.map((model) => ( + + {model.label} + + ))} + + + + savePreference({ + effort: + value === "__default__" + ? null + : (value as CodexPreferences["effort"]), + }) + } + value={selected.effort ?? "__default__"} + > + Reasoning + + Default reasoning + + {(preferences.data?.effortLevels ?? []) + .filter((level): level is NonNullable => + Boolean(level), + ) + .map((level) => ( + + {effortLabel(level)} + + ))} + + + }> + + Account and usage + {tokens ? ( + + {tokens.toLocaleString()} tokens + + ) : null} + + {save.error ? ( +

+ {save.error.message} +

+ ) : null} +
+
); } + +function effortLabel(effort: NonNullable) { + return effort === "xhigh" + ? "Extra high" + : effort[0].toUpperCase() + effort.slice(1); +} diff --git a/app/src/components/channels/conversation-view.tsx b/app/src/components/channels/conversation-view.tsx index fe2e24a..991d18d 100644 --- a/app/src/components/channels/conversation-view.tsx +++ b/app/src/components/channels/conversation-view.tsx @@ -12,11 +12,11 @@ import { useRef, useState, } from "react"; +import { AgentActivityBar } from "@/components/channels/activity-bar"; import { searchableMessageIds, toVisibleChatItems, } from "@/components/channels/chat-messages"; -import { AgentActivityBar } from "@/components/channels/activity-bar"; import { ChatTranscript } from "@/components/channels/chat-transcript"; import { type AgentOption, @@ -37,6 +37,7 @@ export function ConversationView({ messages, busy = false, notice, + activity, agents = [], commands, disabled = false, @@ -61,6 +62,8 @@ export function ConversationView({ busy?: boolean; /** Shown above the composer. An error, or why this conversation is read-only. */ notice?: ReactNode; + /** Durable task status and approvals, kept visible beside the composer while work runs. */ + activity?: ReactNode; agents?: readonly AgentOption[]; /** * The `/` menu for this Bot's granted skills, supplied by the route that owns grant loading. @@ -360,6 +363,11 @@ export function ConversationView({ * else. */} + {activity ? ( +
+ {activity} +
+ ) : null} {searchOpen ? (
diff --git a/app/src/components/channels/deployment-preview-chat.tsx b/app/src/components/channels/deployment-preview-chat.tsx index 1f8c6c6..92fea82 100644 --- a/app/src/components/channels/deployment-preview-chat.tsx +++ b/app/src/components/channels/deployment-preview-chat.tsx @@ -1,3 +1,5 @@ +import { useEffect, useRef } from "react"; +import { TaskRunStatus } from "@/components/tasks/task-run-status"; import type { AgentChannel } from "@/lib/channels/queries"; type PreviewMessage = { @@ -36,14 +38,22 @@ const previewTranscripts: Record = { /** A read-only conversation that shows the channel design without starting a CopilotKit runtime. */ export function DeploymentPreviewChat({ channel }: { channel: AgentChannel }) { const messages = previewTranscripts[channel.id] ?? []; + const transcript = useRef(null); + + useEffect(() => { + const frame = transcript.current; + if (frame) frame.scrollTop = frame.scrollHeight; + }, []); return (
+
- {run.status === "failed" || run.status === "cancelled" ? ( + {onRetry && (run.status === "failed" || run.status === "cancelled") ? ( + +
+ } detail={ agentId === undefined ? null : isWatching ? ( // Manual watch remains active even when there is no current browser action. @@ -162,9 +201,9 @@ function RouteComponent() { * Frosted on purpose: the transcript scrolls beneath a translucent bar, so the header * stops being a hard band and the conversation keeps its depth as it passes under it. */} -
+
{/* Keyed on the displayed name so cold channel loads animate the resolved name, not the id. */} -
+
- - {channel.data?.name ?? "Channel"} - +

+ {channel.data?.name ?? "Channel"} +

+ {activeProfile?.title ? ( +

+ {activeProfile.title} +

+ ) : null} + {(channel.data?.agentIds.length ?? 0) > 1 ? ( setSearch(event.target.value)} + placeholder="Search apps" + value={search} + /> + +
+ +
+ {matching.map((item) => (
-
-
-
{item.title}
-

{item.summary}

+
+ +
+
+ + {item.title} + + {added.has(item.key) ? ( + + Connected + + ) : null} +
+

+ {item.summary} +

-
+
{item.needsCredential ? ( ) : null} - ) : codexEnabled ? ( - - ) : null} - - - - {login && !connected ? ( - +
+ + + + + + - Enter code {login.userCode} + Dark theme - Open ChatGPT, sign in, enter this code, then return here. This - page checks the connection automatically. + Use the dark appearance across OpenBot. - - - + - ) : null} - - {connected && (limit || usage.data?.summary) ? ( - + + +
+
+ + + + + + - Codex allowance + + {connected ? "ChatGPT connected" : "Connect ChatGPT"} + - {usage.data?.summary?.lifetimeTokens - ? `${usage.data.summary.lifetimeTokens.toLocaleString()} lifetime tokens reported. ` - : ""} - Usage refreshes once a minute. ChatGPT subscriptions do not - report a reliable per-turn dollar cost, so Kayco does not - invent one. + {connected + ? `${account.data?.account?.email ?? "Your ChatGPT account"}${ + account.data?.account?.planType + ? ` · ${account.data.account.planType}` + : "" + }. Built-in coworkers now use this account's Codex allowance.` + : codexEnabled + ? "Connect ChatGPT with a one-time device code. OpenBot keeps its own app sign-in separate and never receives your ChatGPT password." + : "This deployment has not enabled the Codex App Server integration."} - {limit ? ( -
- - -
- ) : null}
+ + {connected ? ( + + ) : codexEnabled ? ( + + ) : null} +
- ) : null} - {connected && selectedPreferences ? ( - - - Model and reasoning - - These choices apply to built-in coworkers that use your - connected Codex account. External AG-UI coworkers keep their - own runtime settings. - -
-
- Model - + savePreference({ + model: value === "__default__" ? null : value, + }) + } + value={selectedPreferences.model ?? "__default__"} > - - - - - Deployment default - - {modelChoices.map((model) => ( - - {model.label} + + + + + + Deployment default - ))} - - -
-
- Reasoning effort - + + +
+
+ Reasoning effort + +
-
- {preferenceAction.error ? ( -

- {preferenceAction.error.message} -

- ) : null} - -
- ) : null} + {preferenceAction.error ? ( +

+ {preferenceAction.error.message} +

+ ) : null} + + + ) : null} - {problem || - account.isError || - limits.isError || - usage.isError || - models.isError || - preferences.isError ? ( -

- {problem ?? - account.error?.message ?? - limits.error?.message ?? - usage.error?.message ?? - models.error?.message ?? - preferences.error?.message ?? - "Codex could not be loaded."} -

- ) : null} - - + {problem || + account.isError || + limits.isError || + usage.isError || + models.isError || + preferences.isError ? ( +

+ {problem ?? + account.error?.message ?? + limits.error?.message ?? + usage.error?.message ?? + models.error?.message ?? + preferences.error?.message ?? + "Codex could not be loaded."} +

+ ) : null} + + + + {currentUser?.role === "admin" ? ( + + + } + size="sm" + > + + + + + Connected apps + + Search the app catalogue, connect tools, and choose which + coworkers may use them. + + + + + } + size="sm" + > + + + + + Knowledge sources + + Keep company files searchable with permission-aware + connectors and sync status. + + + + + } + size="sm" + > + + + + + Credential vault + + Store write-only keys and tokens without putting secrets in + a conversation. + + + + + + + ) : null} +
); } -function modelOptions(data: unknown[]) { - const choices = new Map(); - for (const item of data) { - if (!item || typeof item !== "object") continue; - const record = item as Record; - const id = [record.id, record.model, record.slug].find( - (value): value is string => - typeof value === "string" && Boolean(value.trim()), - ); - if (!id) continue; - const label = [record.displayName, record.name, record.label].find( - (value): value is string => - typeof value === "string" && Boolean(value.trim()), - ); - choices.set(id, label ?? id); - } - return [...choices].map(([id, label]) => ({ id, label })); -} - function UsageWindow({ label, window, diff --git a/app/src/routes/_authed/settings/route.tsx b/app/src/routes/_authed/settings/route.tsx index 9aae2f3..43605e4 100644 --- a/app/src/routes/_authed/settings/route.tsx +++ b/app/src/routes/_authed/settings/route.tsx @@ -1,6 +1,6 @@ import { createFileRoute, Outlet } from "@tanstack/react-router"; import { SettingsSidebar } from "@/components/settings/settings-sidebar"; -import { SidebarProvider } from "@/components/ui/sidebar"; +import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; export const Route = createFileRoute("/_authed/settings")({ component: RouteComponent, @@ -21,7 +21,11 @@ function RouteComponent() { } > -
+
+
+ + Settings +
diff --git a/design-qa.md b/design-qa.md new file mode 100644 index 0000000..1b00014 --- /dev/null +++ b/design-qa.md @@ -0,0 +1,126 @@ +# OpenMaus UI harvest design QA + +## Comparison target + +- Source visual truth: `C:\Users\CLondinsky\AppData\Local\Temp\openbot-ui-audit-20260824` + - `hero.png` + - `model-picker.png` + - `marketplace.png` + - `context-menu.png` + - `approval-card.png` + - `app-settings.png` +- Browser-rendered implementation: `http://127.0.0.1:4173` +- Implementation evidence: `C:\Users\CLondinsky\AppData\Local\Temp\openbot-openmaus-implementation-20260824` +- Desktop viewport: 1440 × 900 CSS pixels, device scale factor 1. +- Source pixels: 2880 × 1800 at 2× density. Each source was normalized to 1440 × 900 before comparison. +- Implementation pixels: 1440 × 900 at 1× density. +- Compact viewport: 390 × 844 CSS pixels, device scale factor 1. +- State: authenticated administrator, light theme for source-structure comparisons, connected ChatGPT/Codex sample, pending approval, one connected app. Dark theme was also exercised. + +The OpenMaus captures are the interaction and information-density target, not a branding target. Kayco intentionally keeps its existing Inter typography, light/dark tokens, abstract coworker avatars, web routing, authorization, credential vault, and approval contracts. OpenMaus mascot artwork, desktop chrome, Electron-only Local VM controls, and native voice/calling are outside the visual target. + +## Full-view comparison evidence + +- Home and conversation shell: `comparison-home.png` +- Model picker in the open state: `comparison-model-picker.png` +- Connected-app catalogue: `comparison-marketplace.png` +- Conversation context menu: `comparison-context-menu.png` +- Approval/decision surface: `comparison-approval.png` +- Settings and connections: `comparison-settings.png` + +All comparison images are in the implementation evidence directory above and were opened as combined source-plus-implementation images before judgment. + +## Focused-region evidence + +Focused comparisons were required because the model menu, context menu, approval controls, and catalogue actions were too small to judge from the home view alone. The combined images above show each source state beside the matching Kayco state at the same normalized viewport. Additional implementation captures include: + +- `model-picker-open.png` +- `conversation-menu-open.png` +- `chat-with-approval.png` +- `approval-approved.png` +- `plugins-wide.png` +- `plugins-search.png` +- `settings-connected-fixed.png` +- `settings-dark.png` +- `chat-mobile-contained.png` +- `mobile-navigation-open.png` +- `model-picker-mobile.png` + +## Findings + +- No actionable P0, P1, or P2 findings remain. +- [P3] The existing app shell requests `/favicon.ico` but does not provide a favicon. + - Location: `app/index.html` / browser tab chrome. + - Evidence: the final browser pass had no page errors or failed API responses; its only console error was the missing favicon request. + - Impact: no in-app task is affected, but the browser tab lacks a finished brand mark. + - Fix: add an approved Kayco brand asset when one exists. A placeholder or copied OpenMaus mascot should not be invented for this change. + +## Required fidelity surfaces + +- Fonts and typography: Kayco's existing Inter hierarchy is preserved. Heading, body, metadata, menu, and form weights remain legible at desktop and compact widths; no broken wrapping or unusable truncation remains. +- Spacing and layout rhythm: contact rows, model menu, approval card, and catalogue retain the source's compact density. The catalogue was widened after the first pass so app names, descriptions, and actions no longer collide. Mobile approval content is capped and scrollable so the composer stays reachable. +- Colors and visual tokens: the source's semantic selected, connected, approval, and disabled states are mapped to Kayco tokens. Light and dark themes were rendered; contrast and state separation remained readable. +- Image quality and asset fidelity: no source product imagery was required for the imported interaction patterns. Kayco's existing abstract avatars and Tabler icon family are used; no CSS art, handcrafted SVG, emoji, or placeholder imagery was introduced. +- Copy and content: labels use plain product language—Conversations, Connected apps, Connections, Choose a model, Approve once—and accurately describe Kayco's real permission and account behavior. + +## Interaction and accessibility evidence + +The final Playwright pass exercised: + +- prompt-starter insertion; +- conversation context menu opening; +- model menu opening and model selection; +- approval submission and resumed run state; +- connected-app search filtering; +- settings anchor navigation and dark-theme toggle; +- desktop and 390-pixel compact layouts; +- mobile navigation drawer and mobile model picker; +- horizontal-overflow checks on home, conversation, connected apps, settings, and compact conversation views; +- composer visibility after a large approval card; +- page errors, failed responses, and console errors. + +Result: no page errors, no failed responses, no unexpected console errors, and no horizontal overflow. Keyboard Escape closes the model menu and mobile navigation drawer; visible controls retain semantic labels and focus treatment. + +## Comparison history + +### Pass 1 — blocked + +- [P0] Opening the new model menu crashed because Base UI group labels were outside their radio-group context. +- [P2] The two-column app catalogue was forced into the old prose-width admin column, truncating names and summaries. +- [P2] The preview could not render the connected model, catalogue, or approval states, preventing faithful QA of the harvested interactions. +- [P2] Compact layouts had no navigation trigger, hid the chat model control, and allowed a large approval card to push the composer outside the useful viewport. +- [P2] Preview rate-limit reset timestamps used milliseconds where the UI expected epoch seconds. + +### Fixes applied + +- Moved each dropdown group label inside its Base UI radio group. +- Changed Connected apps to the wide admin shell and kept two readable columns. +- Added realistic, preview-only Codex, app-catalogue, run, and approval responses using the same frontend contracts as production. +- Added compact navigation triggers to app, settings, and administration shells; exposed an icon-sized mobile model trigger. +- Made approval activity height-bounded and scrollable while keeping decision controls and the composer reachable. +- Corrected preview reset timestamps to epoch seconds. +- Added `nativeButton={false}` where Base UI buttons render links, removing accessibility warnings. + +### Pass 2 — passed + +Post-fix evidence is recorded in `model-picker-open.png`, `plugins-wide.png`, `approval-approved.png`, `chat-mobile-contained.png`, `mobile-navigation-open.png`, and the six combined comparison images. The final automated pass completed every primary interaction without runtime or layout failure. + +## Open questions + +- Native voice/calling and Local VM settings were intentionally not copied: they depend on OpenMaus Electron/native services and would be nonfunctional in Kayco's web runtime. +- OpenMaus branding and mascot assets were intentionally not copied because Kayco has its own product identity. + +## Implementation checklist + +- [x] Source and implementation captured at normalized matching desktop dimensions. +- [x] Focused interaction states compared in combined images. +- [x] P0/P1/P2 findings fixed and re-captured. +- [x] Desktop, dark theme, and compact layouts checked. +- [x] Primary interactions and accessibility labels exercised in a real browser. +- [x] Lint, typecheck, frontend tests, and production build run separately. + +## Follow-up polish + +- Add a real Kayco favicon when an approved brand asset is available. + +final result: passed diff --git a/docs/fork.md b/docs/fork.md index a0e93a0..181a3e3 100644 --- a/docs/fork.md +++ b/docs/fork.md @@ -44,6 +44,11 @@ These are durable product directions, not a complete change log: reactions, transcript screenshots, user-selected Codex settings, explicit browser handover, and safe portable coworker templates are Kayco product behavior. +- **Product shell and chat UX:** the outcome-first home, contact-style + conversation roster, in-header model control, visible approval cards, + connected-app catalogue, and consolidated settings are Kayco product + behavior. Compatible interaction patterns are adapted from OpenMausBot; see + [third-party notices](../THIRD_PARTY_NOTICES.md) for provenance and licensing. - **Computer experience:** leased and prewarmed Bot computers, compact action-and-observe results, concurrent capacity with operator recovery, goal-and-step progress, batched form filling, structured table extraction, diff --git a/licenses/OpenMausBot-APACHE-2.0.txt b/licenses/OpenMausBot-APACHE-2.0.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/licenses/OpenMausBot-APACHE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.