diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..4e6548c --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,51 @@ +# Code of Conduct + +Meldwork is a workspace for people and Agents to build together. We want issues, +pull requests, discussions, and community spaces to stay useful, direct, and +respectful. + +## Expected Behavior + +- Be clear, constructive, and respectful when giving feedback. +- Assume good intent while keeping discussions focused on facts, tradeoffs, and outcomes. +- Respect different experience levels, backgrounds, languages, and working styles. +- Keep disagreements technical, specific, and connected to the project. +- Help maintainers keep issues and pull requests focused. +- Protect private prompts, credentials, customer information, and other sensitive data. + +## Unacceptable Behavior + +The following behavior is not acceptable in Meldwork project spaces: + +- Personal attacks, insults, harassment, or sustained disruption. +- Demeaning comments about identity, background, language, ability, or experience. +- Threats, intimidation, stalking, or unwanted contact. +- Publishing another person's private information without consent. +- Spam, trolling, discrimination, or repeated off-topic posting. +- Submitting malicious code, secrets, private data, or intentionally misleading reports. +- Any behavior maintainers reasonably judge to make the project unsafe or unproductive. + +## Maintainer Responsibilities + +Maintainers may edit or remove comments, close issues, limit participation, or +block participants when needed to protect the project community. Enforcement +should be proportional, documented when practical, and focused on keeping +collaboration healthy. + +## Reporting + +Report conduct concerns privately through the repository's GitHub contact or +security reporting channel. Do not open a public issue for sensitive reports. + +Please include: + +- a short description of what happened; +- links, screenshots, or message references when available; +- whether you want follow-up, confidentiality, or no direct reply. + +Reports will be reviewed by project maintainers. We will handle reports with +care and limit access to people who need the information to respond. + +This Code of Conduct applies wherever the project or its maintainers represent +the Meldwork community. It is adapted from the Contributor Covenant and the +community standards used by comparable open-source projects. diff --git a/COMMERCIAL_USE.md b/COMMERCIAL_USE.md index 3f7a6d7..cc9c93d 100644 --- a/COMMERCIAL_USE.md +++ b/COMMERCIAL_USE.md @@ -1,17 +1,14 @@ # Commercial Use And Licensing -Meldwork is source-available under the [Meldwork Non-Commercial Source License](LICENSE). The public repository can be inspected, evaluated, run, and modified for non-commercial purposes, but it is not licensed for commercial use. +Meldwork is licensed under the [Apache License 2.0](LICENSE). Commercial use, +private use, modification, redistribution, and use inside a paid service are +permitted under the license terms. -Any commercial use requires prior written permission from the copyright holder. This includes monetizing Meldwork, reselling or hosting it, embedding it in a proprietary product, using it to provide paid services, or using it as part of a for-profit organization's business operations. +This page is a practical summary and does not replace the license text. Apache +License 2.0 does not grant trademark rights to the Meldwork name, logo, +screenshots, or other brand assets. Third-party software, Agent products, +models, names, logos, and other materials remain subject to their own licenses +and terms. -The copyright holder may offer a separate commercial license for organizations that need permission for: - -- proprietary redistribution or resale of Meldwork or a modified version; -- embedding Meldwork code in a closed-source product; -- offering a hosted, managed, or network-accessible version; -- internal business operations at a for-profit organization; -- separately licensed commercial features, enterprise integrations, support, or managed deployment. - -A commercial license does not change the licenses of third-party dependencies, Agent products, names, models, or logos. The repository license also does not grant trademark rights to the Meldwork name, logo, screenshots, or other brand assets. - -This page is a practical summary and does not replace the license text or legal advice. When a planned use may be commercial, contact the repository owner through GitHub before using, distributing, hosting, reselling, or integrating the product. +Before distributing a product that includes third-party materials, review the +relevant notices and preserve the required attribution and license information. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d8e055..29fbf2c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,8 @@ # Contributing -Meldwork is source-available under the [Meldwork Non-Commercial Source License](LICENSE). Contributions are welcome when they improve the local-first desktop product, documentation, tests, or security posture. +Meldwork is licensed under the [Apache License 2.0](LICENSE). Contributions are welcome when they improve the local-first desktop product, documentation, tests, or security posture. -Unless a separate written agreement applies, a contribution accepted into this repository is available under the repository license. The project may also offer separately licensed commercial editions. Before external code is incorporated into a separately licensed commercial build, the maintainer may require a reviewed contributor-license agreement that grants the necessary relicensing rights. Opening a pull request by itself does not transfer copyright or silently grant those additional rights. +Unless a separate written agreement applies, a contribution accepted into this repository is available under the repository license. Opening a pull request does not transfer copyright beyond the rights granted by the Apache License 2.0. ## Ground Rules @@ -11,8 +11,9 @@ Unless a separate written agreement applies, a contribution accepted into this r - Do not add remote services, telemetry, credential collection, or server dependencies without explicit project approval. - Preserve the local-first boundary: renderer code must not gain unrestricted filesystem, shell, Provider credential, or executable-path access. - Include focused tests for behavior, security boundary, storage, or IPC changes. -- Confirm that all submitted code and assets are yours to license and do not introduce incompatible license obligations. +- Confirm that all submitted code and assets are yours to contribute and do not introduce incompatible license obligations. -## Commercial Editions +## Community Standards -Commercial use of Meldwork requires prior written permission. Commercial editions, proprietary integrations, support, and other paid offerings may use separate terms; see [COMMERCIAL_USE.md](COMMERCIAL_USE.md). +Please follow the [Code of Conduct](CODE_OF_CONDUCT.md) in issues, pull +requests, discussions, and other project spaces. diff --git a/LICENSE b/LICENSE index 763b2de..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,82 +1,201 @@ -Meldwork Non-Commercial Source License 1.0 + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Copyright (c) 2026 Ryder-MHumble. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -This license applies to Meldwork and the source code, documentation, assets, and -other materials in this repository, except where a file or third-party notice -states a different license. + 1. Definitions. -Meldwork is source-available, not open source in the OSI sense. Commercial use is -not permitted without prior written permission from the copyright holder. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -1. Allowed non-commercial use + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -You may download, inspect, run, copy, and modify Meldwork for personal learning, -research, evaluation, security review, and other non-commercial purposes. + "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 may share unmodified or modified copies for non-commercial purposes if you: + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. -- keep this license and all copyright, attribution, and trademark notices; -- clearly mark your changes when you distribute a modified copy; -- do not remove or obscure third-party notices; and -- do not imply that the copyright holder endorses your copy or service. + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. -2. Commercial use requires permission + "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. -You may not use Meldwork for a commercial purpose without a separate written -license or other written permission from the copyright holder. + "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). -Commercial purpose includes, without limitation: + "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. -- selling, renting, sublicensing, or otherwise monetizing Meldwork or a modified - version; -- offering Meldwork or a modified version as a hosted, managed, consulting, - outsourcing, integration, or support service; -- embedding Meldwork code, UI, workflow, or packaged binaries in a proprietary, - paid, or revenue-generating product; -- using Meldwork to provide paid deliverables, paid automation, paid Agent - operations, or other business services to a customer; and -- use by or for a for-profit company, commercial product team, or revenue-driven - organization as part of its business operations. + "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." -If you want to use Meldwork commercially, contact the repository owner and obtain -written permission before using, distributing, hosting, reselling, or integrating -the product. + "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. -3. No trademark license + 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. -This license does not grant rights to use the Meldwork name, logo, product -identity, screenshots, or other brand assets except as needed to describe an -unaltered copy of the software in a truthful, non-confusing way. + 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. Third-party materials + 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: -Third-party software, Agent products, models, names, logos, dependencies, and -other materials remain subject to their own licenses and terms. This license does -not give you rights that the copyright holder does not own. + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and -5. Contributions + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and -Unless a separate written agreement says otherwise, contributions submitted to -this repository may be used under this license. The copyright holder may require -a separate contributor agreement before accepting contributions intended for a -commercially licensed edition. + (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 -6. Termination + (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. -If you violate this license, your rights under it terminate automatically. The -copyright holder may reinstate rights in writing after the violation is cured. + 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. -7. No warranty + 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. -Meldwork is provided "as is", without warranties or conditions of any kind, -including implied warranties of merchantability, fitness for a particular -purpose, title, and non-infringement. You are responsible for evaluating whether -the software is suitable and lawful for your use. + 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. -8. Limitation of liability + 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. -To the maximum extent permitted by law, the copyright holder and contributors -will not be liable for any damages arising from or related to the software or -this license, including direct, indirect, incidental, consequential, special, -exemplary, punitive, or business-interruption damages. + 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. diff --git a/NOTICE b/NOTICE index a9f8a94..3d080db 100644 --- a/NOTICE +++ b/NOTICE @@ -1,9 +1,7 @@ Meldwork Copyright 2026 Ryder-MHumble and Meldwork contributors. -Meldwork is licensed under the Meldwork Non-Commercial Source License. See -LICENSE. Commercial use requires separate written permission from the copyright -holder. +Meldwork is licensed under the Apache License 2.0. See LICENSE. Third-party Agent names, product names, and logos remain the property of their respective owners. They are used only to identify compatible integrations and diff --git a/README.md b/README.md index d50a0ae..d2c9e6f 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,15 @@ - +
-

- Meldwork README banner -

+Meldwork -

- English · 简体中文 -

+[Architecture](architecture.md) · [Agent Connector Contract](docs/agent-connector-sdk.md) · [Contributing](CONTRIBUTING.md) · [Code of Conduct](CODE_OF_CONDUCT.md) + +[English](README.md) | [简体中文](README.zh-CN.md) + +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) +[![Platform](https://img.shields.io/badge/platform-macOS%20Apple%20silicon-black.svg)](https://github.com/Ryder-MHumble/Meldwork/releases) + +
# Meldwork builds the organization layer for AI agents. diff --git a/README.zh-CN.md b/README.zh-CN.md index 82aa4a9..d2d5318 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,18 +1,15 @@ - +
-

- Meldwork README banner -

+Meldwork -

- English · 简体中文 -

+[Architecture](architecture.md) · [Agent Connector Contract](docs/agent-connector-sdk.md) · [Contributing](CONTRIBUTING.md) · [Code of Conduct](CODE_OF_CONDUCT.md) + +[English](README.md) | [简体中文](README.zh-CN.md) + +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) +[![Platform](https://img.shields.io/badge/platform-macOS%20Apple%20silicon-black.svg)](https://github.com/Ryder-MHumble/Meldwork/releases) + +
# Meldwork 为 AI Agent 构建组织层 diff --git a/desktop/package-lock.json b/desktop/package-lock.json index f4207ed..0e6af70 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -7,7 +7,7 @@ "": { "name": "meldwork-desktop", "version": "0.1.3", - "license": "SEE LICENSE IN LICENSE", + "license": "Apache-2.0", "dependencies": { "@agentclientprotocol/sdk": "0.23.0", "@streamparser/json": "0.0.22", diff --git a/desktop/package.json b/desktop/package.json index a4b9132..318cdd6 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -4,7 +4,7 @@ "private": true, "description": "A local-first desktop workspace for multi-Agent collaboration", "author": "Ryder-MHumble", - "license": "SEE LICENSE IN LICENSE", + "license": "Apache-2.0", "engines": { "node": ">=22.12.0" }, diff --git a/desktop/samples/local-echo.connector.json b/desktop/samples/local-echo.connector.json index 0b1606c..eaacaa4 100644 --- a/desktop/samples/local-echo.connector.json +++ b/desktop/samples/local-echo.connector.json @@ -1 +1 @@ -{"manifest":{"connectorId":"external.local-echo-sample","connectorVersion":"1.0.0","credentials":{"mode":"none","slots":[]},"description":"Local SDK sample that returns text without delegating to a built-in Agent.","domains":["general"],"eventProtocolVersion":1,"eventTypes":["Completed","Failed","Cancelled"],"inputTypes":["text"],"invocation":{"recipeId":"sdk.local-echo.v1"},"kind":"agent","label":"Local Echo Connector Sample","license":"LicenseRef-Meldwork-NC","manifestId":"connector-manifest-6ce79bf9cbe282e9f537a1b45d742cc507e21ade5ea2859dff9e007bd4f850db","outboundDestinations":[],"permissionModes":["read-only"],"recordType":"agent-connector-manifest","schemaVersion":1,"session":{"cancel":true,"checkpoint":false,"resume":true,"supported":true},"transport":{"protocol":"json","type":"cli"},"upstream":{"id":"meldwork-sdk","maxVersion":"1.0.0","minVersion":"1.0.0"},"usage":{"costMicros":false,"elapsedMs":false,"inputTokens":false,"outboundBytes":false,"outputTokens":false,"toolCalls":false}},"packageId":"connector-package-f4908cee8fa7acf5d17a403b39bd58cb8d4a1dfec3fbfb43c62c5727ee793cd4","provider":{"config":{},"id":"sdk.local-echo.v1"},"publisher":{"id":"meldwork","name":"Meldwork"},"recordType":"agent-connector-package","schemaVersion":1} +{"manifest":{"connectorId":"external.local-echo-sample","connectorVersion":"1.0.0","credentials":{"mode":"none","slots":[]},"description":"Local SDK sample that returns text without delegating to a built-in Agent.","domains":["general"],"eventProtocolVersion":1,"eventTypes":["Completed","Failed","Cancelled"],"inputTypes":["text"],"invocation":{"recipeId":"sdk.local-echo.v1"},"kind":"agent","label":"Local Echo Connector Sample","license":"Apache-2.0","manifestId":"connector-manifest-6ce79bf9cbe282e9f537a1b45d742cc507e21ade5ea2859dff9e007bd4f850db","outboundDestinations":[],"permissionModes":["read-only"],"recordType":"agent-connector-manifest","schemaVersion":1,"session":{"cancel":true,"checkpoint":false,"resume":true,"supported":true},"transport":{"protocol":"json","type":"cli"},"upstream":{"id":"meldwork-sdk","maxVersion":"1.0.0","minVersion":"1.0.0"},"usage":{"costMicros":false,"elapsedMs":false,"inputTokens":false,"outboundBytes":false,"outputTokens":false,"toolCalls":false}},"packageId":"connector-package-f4908cee8fa7acf5d17a403b39bd58cb8d4a1dfec3fbfb43c62c5727ee793cd4","provider":{"config":{},"id":"sdk.local-echo.v1"},"publisher":{"id":"meldwork","name":"Meldwork"},"recordType":"agent-connector-package","schemaVersion":1} diff --git a/desktop/src/agents/cli/cli-adapters.cjs b/desktop/src/agents/cli/cli-adapters.cjs index f7a2c5e..f2b091f 100644 --- a/desktop/src/agents/cli/cli-adapters.cjs +++ b/desktop/src/agents/cli/cli-adapters.cjs @@ -15,6 +15,7 @@ const { parseMimoOutput, parseOpenCodeOutput, parseOpenCodeReviewOutput, + parsePiOutput, parseWorkBuddyOutput, readHermesFinalResponse, readHermesMessageWatermark, @@ -463,6 +464,7 @@ module.exports = { parseMimoOutput, parseOpenCodeOutput, parseOpenCodeReviewOutput, + parsePiOutput, parseWorkBuddyOutput, prepareCommand, readHermesFinalResponse, diff --git a/desktop/src/agents/cli/cli-event-profiles.cjs b/desktop/src/agents/cli/cli-event-profiles.cjs index c708d24..75e9432 100644 --- a/desktop/src/agents/cli/cli-event-profiles.cjs +++ b/desktop/src/agents/cli/cli-event-profiles.cjs @@ -14,6 +14,7 @@ const { parseMimoOutput, parseOpenCodeOutput, parseOpenCodeReviewOutput, + parsePiOutput, parseWorkBuddyOutput, readHermesFinalResponse, readHermesMessageWatermark, @@ -31,6 +32,7 @@ const { kimiStreamJsonRuntimeEvents, mimoJsonRuntimeEvents, openCodeJsonRuntimeEvents, + piJsonRuntimeEvents, } = require('./cli-runtime-event-mappers.cjs') function createStatelessRuntimeState() { @@ -75,7 +77,7 @@ const FINAL_OUTPUT_PARSERS = Object.freeze({ codex: parseCodexOutput, openclaw: stdout => ({ text: normalizeOpenClawOutput(stdout), sessionRef: '' }), workbuddy: parseWorkBuddyOutput, - pi: parseOpenCodeOutput, + pi: parsePiOutput, kimi: parseKimiOutput, mimo: parseMimoOutput, claude: parseClaudeQwenOutput, @@ -365,7 +367,7 @@ const PI_JSON_EVENTS_PROFILE = profile({ }, createDecoder: createJsonLineParser, createState: createStatelessRuntimeState, - mapEvent: openCodeJsonRuntimeEvents, + mapEvent: piJsonRuntimeEvents, createFinalOutputAccumulator: createFinalOutputAccumulator('pi'), }) @@ -467,7 +469,6 @@ const TRANSPORT_EVENT_PROFILES = Object.freeze({ openclaw: Object.freeze({ legacy: OPENCLAW_TERMINAL_DOCUMENT_PROFILE }), kimi: Object.freeze({ 'stream-json': KIMI_STREAM_JSON_PROFILE }), mimo: Object.freeze({ acp: ACP_JSONRPC_PROFILE }), - pi: Object.freeze({ acp: ACP_JSONRPC_PROFILE }), opencode: Object.freeze({ acp: ACP_JSONRPC_PROFILE }), workbuddy: Object.freeze({ 'stream-json': ANTHROPIC_STREAM_JSON_PROFILE }), }) diff --git a/desktop/src/agents/cli/cli-invocations.cjs b/desktop/src/agents/cli/cli-invocations.cjs index faa95cb..4bdfca1 100644 --- a/desktop/src/agents/cli/cli-invocations.cjs +++ b/desktop/src/agents/cli/cli-invocations.cjs @@ -140,24 +140,20 @@ function invocation(kind, executable, workdir, sessionRef = '', options = {}) { } } if (kind === 'pi') { - if (options.invocationTransport !== 'json') { - return { - command: executable, - args: ['acp', '--pure', '--cwd', workdir], - acpMode: options.sandbox === 'workspace-write' ? 'build' : 'plan', - eventTransport: 'acp', - fallbackTransport: 'json', - fallbackSessionPolicy: 'preserve', - } - } return { command: executable, args: [ - 'run', '--format', 'json', - '--agent', options.sandbox === 'workspace-write' ? 'build' : 'plan', - ...(sessionRef ? ['--session', sessionRef] : []), + '--mode', 'json', + ...(options.sandbox === 'workspace-write' + ? [] + : ['--tools', 'read,grep,find,ls']), + ...(options.provider?.id ? ['--provider', options.provider.id] : []), + ...(options.provider?.model ? ['--model', options.provider.model] : []), + ...(sessionRef ? ['--session-id', sessionRef] : []), + '-p', ], promptArg: true, + eventTransport: 'pi-json-events', } } if (kind === 'kimi') { diff --git a/desktop/src/agents/cli/cli-output-parsers.cjs b/desktop/src/agents/cli/cli-output-parsers.cjs index 1f633ac..c7b64b3 100644 --- a/desktop/src/agents/cli/cli-output-parsers.cjs +++ b/desktop/src/agents/cli/cli-output-parsers.cjs @@ -154,7 +154,29 @@ function ingestStructuredEvent(state, event) { return } - if (['pi', 'mimo', 'opencode'].includes(state.kind)) { + if (state.kind === 'pi') { + if (event.type === 'session' && typeof event.id === 'string') state.sessionRef = event.id + if (event.type === 'message_update' + && event.assistantMessageEvent?.type === 'text_delta' + && typeof event.assistantMessageEvent.delta === 'string') { + appendStructuredText(state, event.assistantMessageEvent.delta, '') + } + if (event.type === 'message_end' + && event.message?.role === 'assistant' + && Array.isArray(event.message.content) + && !state.text.trim()) { + appendStructuredText(state, piMessageContentText(event.message.content), '') + } + if (event.type === 'agent_settled') state.outcome = 'completed' + if (event.type === 'turn_end') { + const reason = String(event.message?.stopReason || event.message?.rawStopReason || '') + state.outcome = reason === 'stop' ? 'completed' : (reason ? stepFinishOutcome(reason) : 'completed') + if (state.outcome === 'failed') markStructuredFailure(state) + } + return + } + + if (['mimo', 'opencode'].includes(state.kind)) { if (typeof event.sessionID === 'string') state.sessionRef = event.sessionID if (event.type === 'text' && event.part?.type === 'text' && typeof event.part.text === 'string') { @@ -437,6 +459,45 @@ function parseMimoOutput(stdout) { return { text: texts.join('\n').trim(), sessionRef, error: errors.join('\n').trim() } } +function piMessageContentText(content) { + return (Array.isArray(content) ? content : []) + .map(part => (part?.type === 'text' && typeof part.text === 'string' ? part.text : '')) + .filter(Boolean) + .join('\n') +} + +function parsePiOutput(stdout) { + const raw = String(stdout || '').trim() + if (!raw) return { text: '', sessionRef: '' } + let sessionRef = '' + const deltas = [] + let finalText = '' + let parsedLine = false + for (const line of raw.split(/\r?\n/)) { + if (!line.trim()) continue + try { + const event = JSON.parse(line) + parsedLine = true + if (event?.type === 'session' && typeof event.id === 'string') sessionRef = event.id + if (event?.type === 'message_update' + && event.assistantMessageEvent?.type === 'text_delta' + && typeof event.assistantMessageEvent.delta === 'string') { + deltas.push(event.assistantMessageEvent.delta) + } + if (event?.type === 'message_end' + && event.message?.role === 'assistant' + && Array.isArray(event.message.content)) { + const text = piMessageContentText(event.message.content) + if (text) finalText = text + } + } catch { /* fall back to raw output when the CLI did not emit JSONL */ } + } + return { + text: finalText.trim() || deltas.join('').trim() || (parsedLine ? '' : raw), + sessionRef, + } +} + function normalizeOpenClawOutput(stdout) { const raw = String(stdout || '').trim() if (!raw) return '' @@ -718,7 +779,15 @@ function classifyCliOutcome(kind, stdout) { : 'partial', } } - if (['pi', 'mimo', 'opencode'].includes(kind)) { + if (kind === 'pi') { + const turn = events.findLast(event => event?.type === 'turn_end') + if (turn) { + const reason = String(turn.message?.stopReason || turn.message?.rawStopReason || '') + return { outcome: reason === 'stop' || !reason ? 'completed' : stepFinishOutcome(reason) } + } + return { outcome: events.some(event => event?.type === 'agent_settled') ? 'completed' : 'partial' } + } + if (['mimo', 'opencode'].includes(kind)) { const finish = events.findLast(event => ( event?.type === 'step_finish' || event?.part?.type === 'step-finish' )) @@ -832,6 +901,7 @@ module.exports = { parseJsonOutputEvents, parseKimiOutput, parseMimoOutput, + parsePiOutput, parseOpenCodeOutput, parseOpenCodeReviewOutput, parseWorkBuddyOutput, diff --git a/desktop/src/agents/cli/cli-runtime-event-mappers.cjs b/desktop/src/agents/cli/cli-runtime-event-mappers.cjs index c7f5a42..ca69c55 100644 --- a/desktop/src/agents/cli/cli-runtime-event-mappers.cjs +++ b/desktop/src/agents/cli/cli-runtime-event-mappers.cjs @@ -345,6 +345,20 @@ function openCodeJsonRuntimeEvents(event) { return [] } +function piJsonRuntimeEvents(event) { + if (!event || typeof event !== 'object') return [] + if (event.type === 'message_update' + && event.assistantMessageEvent?.type === 'text_delta' + && typeof event.assistantMessageEvent.delta === 'string') { + return [{ + type: 'answer_delta', + status: 'running', + delta: event.assistantMessageEvent.delta, + }] + } + return [] +} + function finalOnlyRuntimeEvents() { return [] } @@ -432,4 +446,5 @@ module.exports = { kimiStreamJsonRuntimeEvents, mimoJsonRuntimeEvents, openCodeJsonRuntimeEvents, + piJsonRuntimeEvents, } diff --git a/desktop/src/agents/cli/cli-runtime-events.cjs b/desktop/src/agents/cli/cli-runtime-events.cjs index 8713f82..470544d 100644 --- a/desktop/src/agents/cli/cli-runtime-events.cjs +++ b/desktop/src/agents/cli/cli-runtime-events.cjs @@ -13,6 +13,7 @@ const { parseMimoOutput, parseOpenCodeOutput, parseOpenCodeReviewOutput, + parsePiOutput, parseWorkBuddyOutput, readHermesFinalResponse, readHermesMessageWatermark, @@ -53,6 +54,7 @@ module.exports = { parseMimoOutput, parseOpenCodeOutput, parseOpenCodeReviewOutput, + parsePiOutput, parseWorkBuddyOutput, readHermesFinalResponse, readHermesMessageWatermark, diff --git a/desktop/src/agents/connectors/agent-connector-local.cjs b/desktop/src/agents/connectors/agent-connector-local.cjs index 3851505..8ecc6ff 100644 --- a/desktop/src/agents/connectors/agent-connector-local.cjs +++ b/desktop/src/agents/connectors/agent-connector-local.cjs @@ -48,7 +48,7 @@ const SAMPLE_AGENT_CONNECTOR_MANIFEST = createAgentConnectorManifest({ }, outboundDestinations: [], credentials: { mode: 'none', slots: [] }, - license: 'LicenseRef-Meldwork-NC', + license: 'Apache-2.0', }) const SAMPLE_CREDENTIAL_AGENT_CONNECTOR_MANIFEST = createAgentConnectorManifest({ connectorId: 'external.codex-provider-sample', @@ -81,7 +81,7 @@ const SAMPLE_CREDENTIAL_AGENT_CONNECTOR_MANIFEST = createAgentConnectorManifest( mode: 'credential-ref', slots: [{ slotId: 'openai-api-key', type: 'api-key', required: true }], }, - license: 'LicenseRef-Meldwork-NC', + license: 'Apache-2.0', }) const APPROVED_AGENT_CONNECTOR_MANIFESTS = Object.freeze([ SAMPLE_AGENT_CONNECTOR_MANIFEST, @@ -116,7 +116,7 @@ const SAMPLE_LOCAL_ECHO_AGENT_CONNECTOR_MANIFEST = createAgentConnectorManifest( }, outboundDestinations: [], credentials: { mode: 'none', slots: [] }, - license: 'LicenseRef-Meldwork-NC', + license: 'Apache-2.0', }) const SAMPLE_LOCAL_ECHO_AGENT_CONNECTOR_PACKAGE = createAgentConnectorPackage({ publisher: { id: 'meldwork', name: 'Meldwork' }, diff --git a/desktop/test/agents/cli/cli-adapters-agent-protocols.test.cjs b/desktop/test/agents/cli/cli-adapters-agent-protocols.test.cjs index 00dd8c5..372bdb5 100644 --- a/desktop/test/agents/cli/cli-adapters-agent-protocols.test.cjs +++ b/desktop/test/agents/cli/cli-adapters-agent-protocols.test.cjs @@ -19,6 +19,7 @@ const { parseMimoOutput, parseOpenCodeOutput, parseOpenCodeReviewOutput, + parsePiOutput, parseWorkBuddyOutput, prepareCommand, readHermesFinalResponse, @@ -1062,6 +1063,67 @@ test('MiMo JSON output returns final text and session id', () => { }) }) +test('Pi uses the current non-interactive JSON mode and resumes by session id', () => { + const readOnly = invocation('pi', '/tmp/pi', '/tmp/work', 'pi-session', { + provider: { id: 'zgci', model: 'glm' }, + }) + assert.deepEqual(readOnly.args, [ + '--mode', 'json', + '--tools', 'read,grep,find,ls', + '--provider', 'zgci', + '--model', 'glm', + '--session-id', 'pi-session', + '-p', + ]) + assert.equal(readOnly.promptArg, true) + assert.equal(readOnly.eventTransport, 'pi-json-events') + + const writable = invocation('pi', '/tmp/pi', '/tmp/work', '', { + sandbox: 'workspace-write', + }) + assert.deepEqual(writable.args, ['--mode', 'json', '-p']) +}) + +test('Pi JSON output returns final text and session id from current CLI events', () => { + const raw = [ + JSON.stringify({ + type: 'session', + id: '01a03794-ae3f-72ea-9efd-cd2cd16d88a1', + cwd: '/tmp/work', + }), + JSON.stringify({ + type: 'message_update', + assistantMessageEvent: { type: 'text_delta', contentIndex: 0, delta: 'Pi ' }, + }), + JSON.stringify({ + type: 'message_update', + assistantMessageEvent: { type: 'text_delta', contentIndex: 0, delta: 'reply' }, + }), + JSON.stringify({ + type: 'message_end', + message: { + role: 'assistant', + content: [{ type: 'text', text: 'Pi reply' }], + stopReason: 'stop', + }, + }), + JSON.stringify({ + type: 'turn_end', + message: { role: 'assistant', stopReason: 'stop' }, + }), + JSON.stringify({ type: 'agent_settled' }), + ].join('\n') + assert.deepEqual(parsePiOutput(raw), { + text: 'Pi reply', + sessionRef: '01a03794-ae3f-72ea-9efd-cd2cd16d88a1', + }) + assert.deepEqual(profileOutput('pi', raw), { + text: 'Pi reply', + sessionRef: '01a03794-ae3f-72ea-9efd-cd2cd16d88a1', + outcome: 'completed', + }) +}) + test('Claude and Qwen stream JSON output returns the final reply and session id', () => { for (const kind of ['claude', 'qwen']) { const raw = [ @@ -1598,12 +1660,19 @@ test('every supported built-in Agent produces an explicit completion outcome', ( ].join('\n'), pi: [ JSON.stringify({ - type: 'text', sessionID: 'pi-session', - part: { type: 'text', text: 'Pi Agent reply' }, + type: 'session', id: 'pi-session', version: 3, }), JSON.stringify({ - type: 'step_finish', sessionID: 'pi-session', - part: { type: 'step-finish', reason: 'stop' }, + type: 'message_update', + assistantMessageEvent: { type: 'text_delta', contentIndex: 0, delta: 'Pi Agent reply' }, + }), + JSON.stringify({ + type: 'message_end', + message: { role: 'assistant', content: [{ type: 'text', text: 'Pi Agent reply' }] }, + }), + JSON.stringify({ + type: 'turn_end', + message: { role: 'assistant', stopReason: 'stop', rawStopReason: 'stop' }, }), ].join('\n'), claude: JSON.stringify({ diff --git a/desktop/test/agents/cli/cli-adapters.test.cjs b/desktop/test/agents/cli/cli-adapters.test.cjs index f2141a9..78647b2 100644 --- a/desktop/test/agents/cli/cli-adapters.test.cjs +++ b/desktop/test/agents/cli/cli-adapters.test.cjs @@ -15,6 +15,7 @@ const { parseKimiOutput, parseMimoOutput, parseOpenCodeOutput, + parsePiOutput, parseWorkBuddyOutput, prepareCommand, readHermesFinalResponse, @@ -1521,6 +1522,23 @@ test('Hermes receives selected Skill snapshots through the prompt without native assert.deepEqual(acpSpec.args, ['acp']) }) +test('Pi receives provider settings through supported top-level JSON flags', () => { + const spec = invocation('pi', '/tmp/pi', '/tmp', 'pi-session-123', { + provider: { id: 'zgci', model: 'glm' }, + }) + assert.deepEqual(spec.args, [ + '--mode', 'json', + '--tools', 'read,grep,find,ls', + '--provider', 'zgci', + '--model', 'glm', + '--session-id', 'pi-session-123', + '-p', + ]) + assert.equal(spec.promptArg, true) + assert.equal(spec.eventTransport, 'pi-json-events') + assert.equal(typeof parsePiOutput, 'function') +}) + test('Hermes forwards one image and rejects additional images instead of dropping them', () => { const first = path.resolve('diagram-one.png') const second = path.resolve('diagram-two.png') diff --git a/desktop/test/agents/cli/cli-event-profiles.test.cjs b/desktop/test/agents/cli/cli-event-profiles.test.cjs index 810cb81..a9f8107 100644 --- a/desktop/test/agents/cli/cli-event-profiles.test.cjs +++ b/desktop/test/agents/cli/cli-event-profiles.test.cjs @@ -133,11 +133,7 @@ test('every built-in invocation transport resolves its reusable profile and capa kind: 'mimo', options: { invocationTransport: 'json' }, profileId: 'mimo-json-events-v1', limited: true, }, - { kind: 'pi', profileId: 'acp-jsonrpc-v1', limited: false }, - { - kind: 'pi', options: { invocationTransport: 'json' }, - profileId: 'pi-json-events-v1', limited: true, - }, + { kind: 'pi', profileId: 'pi-json-events-v1', limited: true }, { kind: 'claude', profileId: 'anthropic-stream-json-v1', limited: false }, { kind: 'gemini', profileId: 'gemini-stream-json-v1', limited: false }, { kind: 'opencode', profileId: 'acp-jsonrpc-v1', limited: false }, diff --git a/desktop/test/agents/cli/cli-runtime-event-protocols.test.cjs b/desktop/test/agents/cli/cli-runtime-event-protocols.test.cjs index fef0c46..bd8db42 100644 --- a/desktop/test/agents/cli/cli-runtime-event-protocols.test.cjs +++ b/desktop/test/agents/cli/cli-runtime-event-protocols.test.cjs @@ -54,9 +54,9 @@ const STREAMING_FIXTURES = [ { kind: 'pi', agent: 'Pi Agent', - version: '0.1.0', + version: '0.84.2', protocol: 'pi-json-events', - evidence: 'source-derived', + evidence: 'captured-fixture', file: 'pi.jsonl', expectedTypes: ['answer_delta'], }, diff --git a/desktop/test/fixtures/agent-output/manifest.json b/desktop/test/fixtures/agent-output/manifest.json index f9e4fe7..6c3c325 100644 --- a/desktop/test/fixtures/agent-output/manifest.json +++ b/desktop/test/fixtures/agent-output/manifest.json @@ -5,7 +5,7 @@ { "kind": "hermes", "agent": "Hermes", "version": "0.19.1", "protocol": "terminal-text", "evidence": "captured-fixture", "file": "hermes.txt" }, { "kind": "openclaw", "transport": "legacy", "agent": "OpenClaw", "version": "2026.7.1-2", "protocol": "openclaw-terminal-document", "evidence": "captured-fixture", "file": "openclaw.json" }, { "kind": "workbuddy", "agent": "WorkBuddy", "version": "2.115.0", "protocol": "workbuddy-terminal-result-json", "evidence": "source-derived", "file": "workbuddy.jsonl", "variants": [{ "transport": "stream-json", "agent": "WorkBuddy", "version": "2.115.0", "protocol": "anthropic-stream-json", "evidence": "source-derived", "file": "workbuddy-stream-json.jsonl" }] }, - { "kind": "pi", "agent": "Pi Agent", "version": "0.1.0", "protocol": "pi-json-events", "evidence": "source-derived", "file": "pi.jsonl" }, + { "kind": "pi", "agent": "Pi Agent", "version": "0.84.2", "protocol": "pi-json-events", "evidence": "captured-fixture", "file": "pi.jsonl" }, { "kind": "kimi", "transport": "stream-json", "agent": "Kimi", "version": "0.19.2", "protocol": "assistant-jsonl", "evidence": "captured-fixture", "file": "kimi.jsonl" }, { "kind": "mimo", "agent": "MiMo", "version": "0.1.0", "protocol": "mimo-json-events", "evidence": "captured-fixture", "file": "mimo.jsonl" }, { "kind": "claude", "agent": "Claude", "version": "2.1.165", "protocol": "anthropic-stream-json", "evidence": "captured-fixture", "file": "claude.jsonl" }, diff --git a/desktop/test/fixtures/agent-output/pi.jsonl b/desktop/test/fixtures/agent-output/pi.jsonl index ae1c8df..0371ed1 100644 --- a/desktop/test/fixtures/agent-output/pi.jsonl +++ b/desktop/test/fixtures/agent-output/pi.jsonl @@ -1,2 +1,8 @@ -{"type":"text","sessionID":"pi-fixture-session","part":{"type":"text","text":"Pi Agent fixture reply"}} -{"type":"step_finish","sessionID":"pi-fixture-session","part":{"type":"step-finish","reason":"stop"}} +{"type":"session","version":3,"id":"pi-fixture-session","cwd":"/tmp/work"} +{"type":"agent_start"} +{"type":"turn_start"} +{"type":"message_update","assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Pi Agent fixture reply"}} +{"type":"message_end","message":{"role":"assistant","content":[{"type":"text","text":"Pi Agent fixture reply"}],"stopReason":"stop","rawStopReason":"stop"}} +{"type":"turn_end","message":{"role":"assistant","content":[{"type":"text","text":"Pi Agent fixture reply"}],"stopReason":"stop","rawStopReason":"stop"}} +{"type":"agent_end"} +{"type":"agent_settled"} diff --git a/desktop/test/workspace/local-workspace-native-streaming.test.cjs b/desktop/test/workspace/local-workspace-native-streaming.test.cjs index 11c6ea7..c21d292 100644 --- a/desktop/test/workspace/local-workspace-native-streaming.test.cjs +++ b/desktop/test/workspace/local-workspace-native-streaming.test.cjs @@ -39,6 +39,61 @@ test('direct Hermes conversations reuse one persistent ACP runtime key across me assert.equal(calls[1].runOptions.acpPersistenceKey, calls[0].runOptions.acpPersistenceKey) }) +test('direct Pi conversations reuse the native JSON session across messages', async (t) => { + const { directory, calls, options } = fixture() + t.after(() => fs.rmSync(directory, { recursive: true, force: true })) + options.detectAgents = async () => [{ + kind: 'pi', name: 'Pi Agent', executable: '/tmp/pi', version: '0.84.2', + }] + options.runAgent = async (agent, prompt, workdir, runOptions) => { + calls.push({ agent, prompt, workdir, runOptions }) + const sessionRef = runOptions.sessionRef || 'pi-json-session' + await runOptions.onSessionRef(sessionRef) + return { text: `pi reply ${calls.length}`, sessionRef, outcome: 'completed' } + } + const workspace = new LocalWorkspace(options) + await workspace.refreshAgents() + const group = workspace.createGroup({ + name: 'Pi direct', agentKinds: ['pi'], workdir: directory, + conversationType: 'direct', directAgentKind: 'pi', + }) + + await workspace.sendMessage({ groupId: group.id, text: 'first' }) + await workspace.sendMessage({ groupId: group.id, text: 'second' }) + + assert.deepEqual(calls.map(call => call.agent.kind), ['pi', 'pi']) + assert.deepEqual(calls.map(call => call.runOptions.sessionRef), ['', 'pi-json-session']) + assert.deepEqual(calls.map(call => call.runOptions.sessionTransport), ['', '']) +}) + +test('group conversations dispatch Pi alongside other available Agents', async (t) => { + const { directory, calls, options } = fixture() + t.after(() => fs.rmSync(directory, { recursive: true, force: true })) + options.detectAgents = async () => [ + { kind: 'pi', name: 'Pi Agent', executable: '/tmp/pi', version: '0.84.2' }, + { kind: 'codex', name: 'Codex', executable: '/tmp/codex', version: '0.144.0' }, + ] + options.runAgent = async (agent, prompt, workdir, runOptions) => { + calls.push({ agent, prompt, workdir, runOptions }) + const sessionRef = runOptions.sessionRef || `${agent.kind}-session` + await runOptions.onSessionRef(sessionRef) + return { text: `${agent.kind} reply`, sessionRef, outcome: 'completed' } + } + const workspace = new LocalWorkspace(options) + await workspace.refreshAgents() + const group = workspace.createGroup({ + name: 'Pi group', agentKinds: ['pi', 'codex'], workdir: directory, + }) + + await workspace.sendMessage({ + groupId: group.id, text: 'ask the group', targetKinds: ['pi', 'codex'], + }) + + assert.deepEqual(calls.map(call => call.agent.kind).sort(), ['codex', 'pi']) + const messages = workspace.snapshot().messages.filter(item => item.role === 'agent') + assert.deepEqual(messages.map(item => item.agentKind).sort(), ['codex', 'pi']) +}) + test('Workspace stream bridge redacts split credentials and paths while preserving tools', async (t) => { const { directory, options } = fixture() t.after(() => fs.rmSync(directory, { recursive: true, force: true })) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b5802d0..0f178b7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7,7 +7,7 @@ "": { "name": "meldwork-frontend", "version": "0.1.3", - "license": "SEE LICENSE IN LICENSE", + "license": "Apache-2.0", "dependencies": { "@vicons/ionicons5": "^0.12.0", "dompurify": "^3.4.13", diff --git a/frontend/package.json b/frontend/package.json index 393861a..f1b6085 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,7 @@ "private": true, "description": "Meldwork local-first multi-Agent collaboration workspace", "author": "Ryder-MHumble", - "license": "SEE LICENSE IN LICENSE", + "license": "Apache-2.0", "engines": { "node": ">=22.12.0" },