@aipoch/open-science 0.0.0-stage → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLI.md +814 -0
- package/LICENSE +201 -0
- package/README.md +284 -2
- package/appimage-launcher.mjs +90 -0
- package/browser-import.d.ts +66 -0
- package/browser-import.mjs +389 -0
- package/cli.mjs +1845 -0
- package/codex-login.mjs +298 -0
- package/config-root.mjs +65 -0
- package/index.d.ts +677 -0
- package/index.mjs +761 -0
- package/installation-startup.cjs +205 -0
- package/locate-app.mjs +95 -0
- package/locate-backend.mjs +63 -0
- package/package.json +45 -4
- package/runtime-control.mjs +51 -0
- package/runtime-package.mjs +19 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 AIPOCH
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,285 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @aipoch/open-science
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[AIPOCH Open-Science](https://github.com/aipoch/open-science) is an open-source, local-first,
|
|
4
|
+
model-agnostic AI research workbench. Use scientific Agents, Python and R Notebooks, and
|
|
5
|
+
scientific data connectors to turn research questions into reports, tables and figures.
|
|
6
|
+
|
|
7
|
+
This package provides the **standalone CLI, local Web UI and Node.js SDK**. npm installs the
|
|
8
|
+
matching platform package with the backend and resources; the desktop app is not required.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
Install [Node.js](https://nodejs.org/) **22.13.0 or later**, then run:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install --global @aipoch/open-science
|
|
16
|
+
open-science --help
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Supported platforms are macOS Apple Silicon/Intel, Linux ARM64/x64 with glibc, and Windows x64.
|
|
20
|
+
Linux musl (including Alpine) and Windows ARM64 are not supported by the published native packages.
|
|
21
|
+
Keep npm optional dependencies enabled: they contain the platform runtime. If startup reports a
|
|
22
|
+
missing native package, reinstall with `npm install --global @aipoch/open-science --include=optional`.
|
|
23
|
+
|
|
24
|
+
Linux also needs working credential storage and Notebook sandbox prerequisites; see the
|
|
25
|
+
[Linux setup requirements](https://github.com/aipoch/open-science/blob/main/docs/standalone-runtime.md#linux-prerequisites).
|
|
26
|
+
Python/R and an Agent runtime/model provider are configured separately during setup.
|
|
27
|
+
For installations that include Node itself, see the
|
|
28
|
+
[Homebrew and Release archive options](https://github.com/aipoch/open-science/blob/main/docs/standalone-runtime.md).
|
|
29
|
+
|
|
30
|
+
## Get started
|
|
31
|
+
|
|
32
|
+
Start the local service and request its browser URL:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
open-science start --no-open
|
|
36
|
+
open-science url
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Open the printed URL, complete the setup checks, configure an Agent runtime and model provider,
|
|
40
|
+
and create your first research project. Set up a Python or R Notebook runtime if your work needs
|
|
41
|
+
code execution. The URL contains a local authentication token; keep it private.
|
|
42
|
+
For setup from the terminal, follow the
|
|
43
|
+
[CLI first-run guide](https://github.com/aipoch/open-science/blob/main/packages/open-science/CLI.md#first-run-setup-codex).
|
|
44
|
+
|
|
45
|
+
After setup, submit a task from the terminal:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
open-science doctor --json
|
|
49
|
+
open-science project create "First research" --json
|
|
50
|
+
open-science run --project "First research" --prompt "Draft a research plan for comparing two analysis methods." --wait --jsonl
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Confirm `ready: true` in the doctor output before submitting work. The CLI accepts a Project ID
|
|
54
|
+
or an exact, unambiguous name; SDK calls use IDs. Closing the browser does not stop the service:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
open-science status --json
|
|
58
|
+
open-science stop
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Stopping the service affects all connected clients. For automated tasks, review the
|
|
62
|
+
[permissions and output options](https://github.com/aipoch/open-science/blob/main/packages/open-science/CLI.md)
|
|
63
|
+
and the safety guidance below.
|
|
64
|
+
|
|
65
|
+
## Update or uninstall
|
|
66
|
+
|
|
67
|
+
Stop services started by this installation before replacing it. Use the same `--config-root`
|
|
68
|
+
when stopping and restarting a custom profile.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
open-science stop
|
|
72
|
+
npm install --global @aipoch/open-science@latest
|
|
73
|
+
open-science start --no-open
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
To uninstall, stop the service and run `npm uninstall --global @aipoch/open-science`.
|
|
77
|
+
Uninstalling the npm package retains research data and credentials. Review
|
|
78
|
+
[release compatibility](https://github.com/aipoch/open-science/blob/main/docs/compatibility.md)
|
|
79
|
+
before changing versions; replacing program files does not reverse data migrations.
|
|
80
|
+
|
|
81
|
+
## Documentation
|
|
82
|
+
|
|
83
|
+
- [CLI guide](https://github.com/aipoch/open-science/blob/main/packages/open-science/CLI.md) - installation, daemon lifecycle, task automation, artifacts, and exit codes
|
|
84
|
+
- [Product overview](https://github.com/aipoch/open-science#readme) - research workflows, screenshots and desktop downloads
|
|
85
|
+
- [Report an issue](https://github.com/aipoch/open-science/issues) - bugs and feature requests
|
|
86
|
+
- [Release compatibility](https://github.com/aipoch/open-science/blob/main/docs/compatibility.md) - stable interfaces, versioning, and data upgrades
|
|
87
|
+
- [Intended use](https://github.com/aipoch/open-science/blob/main/docs/usage-scope.md) and [known limitations](https://github.com/aipoch/open-science/blob/main/docs/known-limitations.md) - workflow precautions, data disclosure, and execution boundaries
|
|
88
|
+
|
|
89
|
+
## Safety and integration
|
|
90
|
+
|
|
91
|
+
- **Scope:** Authenticated control of a local Open-Science service; the client does not provide an
|
|
92
|
+
independent execution sandbox or isolate the service's Agents and tools.
|
|
93
|
+
- **Controls:** Requests use the service's permission profiles, existing grants, and host-owned
|
|
94
|
+
runtime controls. Disabling interactive permission prompts denies requests needing a human; it
|
|
95
|
+
does not erase previously granted authority.
|
|
96
|
+
- **Remaining risks:** A successful automated run can modify allowed files, contact configured
|
|
97
|
+
services, or start remote jobs. A timeout or aborted wait stops observation, not the underlying run.
|
|
98
|
+
Research outputs still need scientific review.
|
|
99
|
+
- **Safe integration:** Protect local service credentials, review the effective profile/grants and
|
|
100
|
+
recipients, and use isolated test data before unattended operation. Cancel explicitly and confirm
|
|
101
|
+
the resulting task/job state. Inspect downloaded or exported artifacts before sharing.
|
|
102
|
+
|
|
103
|
+
## Browser-held package import
|
|
104
|
+
|
|
105
|
+
Use `@aipoch/open-science/browser-import` to send a downloaded `.science` Blob to the desktop app.
|
|
106
|
+
See the [browser integration guide](https://github.com/aipoch/open-science/blob/main/packages/open-science/BROWSER_IMPORT.md) for the click flow, API, security contract,
|
|
107
|
+
limits, and browser requirements. The desktop never downloads the online package URL.
|
|
108
|
+
|
|
109
|
+
## SDK quick start
|
|
110
|
+
|
|
111
|
+
Install the package in your Node.js project (a global CLI install does not make it importable):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm install @aipoch/open-science
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Start and configure the local service as above. Save the example in an `.mjs` file and run it
|
|
118
|
+
with Node.js. It creates a Project and uses its ID for subsequent requests:
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
import { connectToOpenScience } from '@aipoch/open-science'
|
|
122
|
+
|
|
123
|
+
const client = await connectToOpenScience()
|
|
124
|
+
const project = await client.createProject({ name: 'Evidence review' })
|
|
125
|
+
const run = await client.startRun({
|
|
126
|
+
project: project.id,
|
|
127
|
+
prompt: 'Summarize the evidence.',
|
|
128
|
+
permissionProfile: 'auto',
|
|
129
|
+
permissionPrompts: 'none' // Deny requests that would require a human; keep existing permission rules.
|
|
130
|
+
})
|
|
131
|
+
const result = await client.waitForRun(run.id)
|
|
132
|
+
console.log(result.output)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Session, Project-default, and global Agent-routing configuration use the same authenticated local
|
|
136
|
+
API:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
const configuration = await client.getSessionConfiguration(run.sessionId)
|
|
140
|
+
await client.updateSessionConfiguration(run.sessionId, {
|
|
141
|
+
expectedRevision: configuration.revision,
|
|
142
|
+
memoryEnabled: false
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const defaults = await client.getProjectSessionDefaults(project.id)
|
|
146
|
+
await client.updateProjectSessionDefaults(project.id, {
|
|
147
|
+
expectedUpdatedAt: defaults.updatedAt,
|
|
148
|
+
patch: { permissionProfile: 'auto' }
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
await client.updateAgentRouting({
|
|
152
|
+
framework: 'codex',
|
|
153
|
+
reviewer: { mode: 'inherit' },
|
|
154
|
+
subagent: { mode: 'inherit' }
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const runtimes = await client.listRuntimes()
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Session updates use `revision`; Project-default updates use `updatedAt`. Both reject stale writes.
|
|
161
|
+
Project defaults are copied only when a Session is created, with precedence `startRun` request,
|
|
162
|
+
Project defaults, application settings, then provider default. Agent-routing updates are atomic and
|
|
163
|
+
never return provider credentials.
|
|
164
|
+
|
|
165
|
+
Agent runtime listings expose only framework, readiness, version, and managed/external source. They
|
|
166
|
+
do not expose executable paths.
|
|
167
|
+
|
|
168
|
+
SDK requests have a 30-second default deadline that remains active while the response body is being
|
|
169
|
+
consumed. Override the client default with `requestTimeoutMs`, or pass `{ signal, timeoutMs }` as the
|
|
170
|
+
final options argument to an individual request method. `downloadArtifact` keeps the deadline active
|
|
171
|
+
while its returned `Response` body is streaming. `waitForRun` applies its overall `timeoutMs` and
|
|
172
|
+
caller signal to every in-flight polling request as well as the delay between polls. Each poll is
|
|
173
|
+
bounded by the smaller of the client's `requestTimeoutMs` and the remaining total wait budget.
|
|
174
|
+
A request timeout fails the wait immediately; it does not silently retry. Without a total
|
|
175
|
+
`timeoutMs`, waiting has no overall deadline. `pollIntervalMs` defaults to 250 and must be a finite
|
|
176
|
+
positive number.
|
|
177
|
+
|
|
178
|
+
Aborting or timing out a wait stops only the local observation. The Run may continue on the host;
|
|
179
|
+
call `getRun(run.id)` to check it, or explicitly call `cancelRun(run.id)` to stop it. With
|
|
180
|
+
`returnOnAttention: true`, a returned Run may still have `status: 'running'` and need a Plan decision before it can finish. Ordinary permissions do not populate Run attention.
|
|
181
|
+
|
|
182
|
+
```js
|
|
183
|
+
const waitController = new AbortController()
|
|
184
|
+
const result = await client.waitForRun(run.id, {
|
|
185
|
+
timeoutMs: 120_000, // Total wait budget; each poll still has the client request deadline.
|
|
186
|
+
pollIntervalMs: 500,
|
|
187
|
+
returnOnAttention: true,
|
|
188
|
+
signal: waitController.signal // Aborts observation; does not call cancelRun.
|
|
189
|
+
})
|
|
190
|
+
console.log(result.status, result.attention)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
For retry-safe project creation and Run starts, pass the same `idempotencyKey` in the final options
|
|
194
|
+
argument on every attempt. The daemon replays the first response for up to 24 hours while it remains
|
|
195
|
+
running; reusing a key with a different request body fails with `idempotency_conflict`. If the
|
|
196
|
+
bounded replay registry is full, a new unique key fails with `idempotency_unavailable` rather than
|
|
197
|
+
evicting an existing guarantee. A lost response after submission does not prove that the Run was
|
|
198
|
+
not started. Retry with the original key and identical request while that daemon is still running,
|
|
199
|
+
or query the known Run ID. The replay registry is process-local: a daemon restart loses it, so
|
|
200
|
+
reusing a key after restart is not a durable exactly-once guarantee.
|
|
201
|
+
|
|
202
|
+
The `project` request field and the `listSessions(projectId)` argument both require a Project ID.
|
|
203
|
+
Project display names are not accepted as routing identifiers.
|
|
204
|
+
|
|
205
|
+
SDK and HTTP callers must supply an absolute `cwd`. Open-Science canonicalizes and validates it,
|
|
206
|
+
persists it as the Session working directory, and returns the effective path on every Run. Supplying
|
|
207
|
+
`cwd` with `sessionId` is allowed only when both paths resolve to the same directory. Omit `cwd` to
|
|
208
|
+
use a managed workspace. External working directories remain caller-owned and are never removed by
|
|
209
|
+
Open-Science.
|
|
210
|
+
|
|
211
|
+
For live automation feedback, subscribe before starting the Run. `run.progress` reports ordered
|
|
212
|
+
provider-neutral phases and emits a progress heartbeat every ten seconds until the first visible
|
|
213
|
+
provider output. This progress heartbeat describes Run activity; it is separate from the filtered
|
|
214
|
+
connection heartbeat that keeps an otherwise-idle WebSocket alive. The timer starts after Task has
|
|
215
|
+
prepared the Session and registered its Run; Session creation or resume time before registration is
|
|
216
|
+
outside this event stream:
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
const abortController = new AbortController()
|
|
220
|
+
const events = client.events({ signal: abortController.signal })
|
|
221
|
+
await events.ready
|
|
222
|
+
|
|
223
|
+
const progress = (async () => {
|
|
224
|
+
for await (const event of events) {
|
|
225
|
+
if (event.type === 'run.progress') {
|
|
226
|
+
console.log(event.data.phase, event.data.elapsedMs, event.data.heartbeat)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
})()
|
|
230
|
+
|
|
231
|
+
const observedRun = await client.startRun({
|
|
232
|
+
project: project.id,
|
|
233
|
+
prompt: 'Summarize the evidence.',
|
|
234
|
+
permissionProfile: 'auto'
|
|
235
|
+
})
|
|
236
|
+
const observedResult = await client.waitForRun(observedRun.id)
|
|
237
|
+
abortController.abort()
|
|
238
|
+
await progress
|
|
239
|
+
console.log(observedResult.output)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`events.ready` rejects if the socket fails, closes, or receives no liveness frame before opening.
|
|
243
|
+
After opening, the iterator fails with `timeout` when no event or connection heartbeat arrives for
|
|
244
|
+
30 seconds. Pass `idleTimeoutMs` to change that connection-liveness window; it does not limit model,
|
|
245
|
+
Notebook, permission-approval, or other Run duration. Connection heartbeats are control frames and
|
|
246
|
+
are not yielded to consumers. Malformed frames and a consumer backlog above 1024 events terminate
|
|
247
|
+
the iterator with `event_stream_invalid_message` or `event_stream_overflow` instead of throwing
|
|
248
|
+
outside the iterator or growing memory without a bound.
|
|
249
|
+
|
|
250
|
+
Every Run event includes top-level `sequence`, `runId`, `sessionId`, and `projectId` fields in
|
|
251
|
+
addition to its existing `type` and `data`. After an established socket closes unexpectedly, the SDK
|
|
252
|
+
reconnects with its last sequence and the daemon replays the retained suffix. Replay is bounded and
|
|
253
|
+
process-local. If the daemon restarted or the suffix was evicted, the iterator yields
|
|
254
|
+
`stream.resync-required` with reason `stream-changed` or `cursor-expired`; read the Run or Session
|
|
255
|
+
through the HTTP API to restore authoritative state.
|
|
256
|
+
|
|
257
|
+
Plan First runs can opt into actionable waiting with returnOnAttention. When the returned Run has
|
|
258
|
+
attention.kind equal to plan-approval, use getSessionPlan and respondSessionPlan. Calling waitForRun
|
|
259
|
+
without returnOnAttention keeps the original terminal-only behavior.
|
|
260
|
+
|
|
261
|
+
Automatic review and Specialist binding reuse existing Session JSON fields. Delegation adds
|
|
262
|
+
delegationPolicy with values allow or deny; an omitted historical value restores as allow.
|
|
263
|
+
|
|
264
|
+
To stop a still-running task instead of waiting for it, cancel it explicitly. Cancellation waits for
|
|
265
|
+
provider work and application finalization to drain before returning the terminal Run:
|
|
266
|
+
|
|
267
|
+
```js
|
|
268
|
+
const cancelled = await client.cancelRun(run.id)
|
|
269
|
+
console.log(cancelled.status) // cancelled
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The client discovers the local daemon and reads its authentication token from the Open-Science config
|
|
273
|
+
directory. HTTP calls send the token in request headers. `events()` authenticates its WebSocket using
|
|
274
|
+
a `token` URL query parameter; treat WebSocket URLs and request logs as sensitive. Tokens are
|
|
275
|
+
not included in normal command output.
|
|
276
|
+
|
|
277
|
+
### Automation new-session defaults
|
|
278
|
+
|
|
279
|
+
`project session-defaults show|update` manages defaults for new Sessions created through the
|
|
280
|
+
Task CLI/API. Explicit run options override these defaults. Existing Sessions and desktop New
|
|
281
|
+
conversation drafts are not changed.
|
|
282
|
+
|
|
283
|
+
`--provider-default-model` keeps following the provider-owned default instead of pinning the
|
|
284
|
+
first model in its catalog. An explicit `--model` stays fixed. If a saved selection becomes
|
|
285
|
+
unavailable, choose a replacement explicitly or wait for it to become available again.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
3
|
+
import { createReadStream } from 'node:fs'
|
|
4
|
+
import { cp, lstat, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
|
|
5
|
+
import { dirname, isAbsolute, join } from 'node:path'
|
|
6
|
+
import { homedir } from 'node:os'
|
|
7
|
+
import { spawn } from 'node:child_process'
|
|
8
|
+
import { fileURLToPath } from 'node:url'
|
|
9
|
+
|
|
10
|
+
// An AppImage mount belongs to the foreground CLI invocation. A detached server must never read
|
|
11
|
+
// native libraries/resources from that disappearing mount. Retain only this immutable, hashed
|
|
12
|
+
// installation payload in the user's cache; business settings and secrets stay in their usual roots.
|
|
13
|
+
export async function stageAppImageCli(image, backend, nodeRuntime, cache) {
|
|
14
|
+
if (!isAbsolute(image)) throw new Error('Expected an absolute AppImage path.')
|
|
15
|
+
const digest = createHash('sha256')
|
|
16
|
+
for await (const bytes of createReadStream(image)) digest.update(bytes)
|
|
17
|
+
const identity = digest.digest('hex')
|
|
18
|
+
await mkdir(cache, { recursive: true, mode: 0o700 })
|
|
19
|
+
const info = await lstat(cache)
|
|
20
|
+
if (
|
|
21
|
+
!info.isDirectory() ||
|
|
22
|
+
(process.platform !== 'win32' && (info.mode & 0o022) !== 0) ||
|
|
23
|
+
(process.getuid && info.uid !== process.getuid())
|
|
24
|
+
)
|
|
25
|
+
throw new Error('AppImage CLI cache must be a directory owned by the current user.')
|
|
26
|
+
const destination = join(cache, identity)
|
|
27
|
+
const marker = JSON.stringify({ image: identity })
|
|
28
|
+
const valid = async () => {
|
|
29
|
+
try {
|
|
30
|
+
const info = await lstat(destination)
|
|
31
|
+
return (
|
|
32
|
+
info.isDirectory() &&
|
|
33
|
+
(process.platform === 'win32' || (info.mode & 0o022) === 0) &&
|
|
34
|
+
(!process.getuid || info.uid === process.getuid()) &&
|
|
35
|
+
(await readFile(join(destination, 'complete.json'), 'utf8')) === marker
|
|
36
|
+
)
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error.code === 'ENOENT') return false
|
|
39
|
+
throw error
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (await valid()) return destination
|
|
43
|
+
const temporary = join(cache, `.${identity}-${randomUUID()}`)
|
|
44
|
+
await mkdir(temporary, { mode: 0o700 })
|
|
45
|
+
try {
|
|
46
|
+
await cp(backend, join(temporary, 'backend'), { recursive: true, dereference: true })
|
|
47
|
+
await cp(nodeRuntime, join(temporary, 'node-runtime'), { recursive: true, dereference: true })
|
|
48
|
+
await writeFile(join(temporary, 'complete.json'), marker, { flag: 'wx', mode: 0o600 })
|
|
49
|
+
try {
|
|
50
|
+
await rename(temporary, destination)
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const exists =
|
|
53
|
+
['EEXIST', 'ENOTEMPTY'].includes(error.code) ||
|
|
54
|
+
(process.platform === 'win32' && error.code === 'EPERM')
|
|
55
|
+
if (!exists || !(await valid())) throw error
|
|
56
|
+
}
|
|
57
|
+
return destination
|
|
58
|
+
} finally {
|
|
59
|
+
await rm(temporary, { recursive: true, force: true })
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function main() {
|
|
64
|
+
const backend = dirname(fileURLToPath(import.meta.url))
|
|
65
|
+
const payload = await stageAppImageCli(
|
|
66
|
+
process.argv[2],
|
|
67
|
+
backend,
|
|
68
|
+
join(backend, '../node-runtime'),
|
|
69
|
+
join(process.env.XDG_CACHE_HOME || join(homedir(), '.cache'), 'open-science', 'appimage-cli')
|
|
70
|
+
)
|
|
71
|
+
const env = { ...process.env }
|
|
72
|
+
delete env.ELECTRON_RUN_AS_NODE
|
|
73
|
+
const child = spawn(
|
|
74
|
+
join(payload, 'node-runtime/node'),
|
|
75
|
+
[join(payload, 'backend/cli.mjs'), ...process.argv.slice(3)],
|
|
76
|
+
{ env, stdio: 'inherit' }
|
|
77
|
+
)
|
|
78
|
+
child.once('error', (error) => {
|
|
79
|
+
console.error(error.message)
|
|
80
|
+
process.exitCode = 1
|
|
81
|
+
})
|
|
82
|
+
child.once('exit', (code) => {
|
|
83
|
+
process.exitCode = code ?? 1
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1])
|
|
87
|
+
await main().catch((error) => {
|
|
88
|
+
console.error(error.message)
|
|
89
|
+
process.exitCode = 1
|
|
90
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export type BrowserImportReadinessMode = 'attempts' | 'wait-for-launch'
|
|
2
|
+
export type BrowserImportPhase = 'waiting-for-receiver' | 'transferring'
|
|
3
|
+
export type BrowserImportProgress = { sentBytes: number; totalBytes: number }
|
|
4
|
+
export type BrowserImportFailureCode =
|
|
5
|
+
| 'unsupported-browser'
|
|
6
|
+
| 'timeout'
|
|
7
|
+
| 'readiness-exhausted'
|
|
8
|
+
| 'incompatible'
|
|
9
|
+
| 'launch-failed'
|
|
10
|
+
| 'transfer-failed'
|
|
11
|
+
| 'cancelled'
|
|
12
|
+
export class BrowserImportError extends Error {
|
|
13
|
+
readonly code: BrowserImportFailureCode
|
|
14
|
+
constructor(code: BrowserImportFailureCode, message: string)
|
|
15
|
+
}
|
|
16
|
+
/** Synchronous user-click launch request. Returning does not prove installation or delivery.
|
|
17
|
+
* Throws BrowserImportError: unsupported-browser (no navigation API), launch-failed (navigation threw). */
|
|
18
|
+
export function launchOpenScience(): void
|
|
19
|
+
export type BrowserImportDiscoveryOptions = {
|
|
20
|
+
signal?: AbortSignal
|
|
21
|
+
/** Must match the page origin. Inferred from location in browsers. */
|
|
22
|
+
origin?: string
|
|
23
|
+
/** A single configured endpoint, never a range to scan. Defaults to 49151. */
|
|
24
|
+
port?: number
|
|
25
|
+
timeoutMs?: number
|
|
26
|
+
}
|
|
27
|
+
export type BrowserImportUnavailable = {
|
|
28
|
+
/** unavailable also covers opaque CORS/local-network denials; it does not mean not installed. */
|
|
29
|
+
status: 'unavailable' | 'unsupported-browser' | 'incompatible' | 'timeout' | 'aborted'
|
|
30
|
+
}
|
|
31
|
+
/** Probes a running, reachable service, not installation/protocol registration. May prompt for
|
|
32
|
+
* local-network permission. Advisory only; the secret-bound handshake authenticates the receiver. */
|
|
33
|
+
export function checkBrowserImportAvailability(
|
|
34
|
+
options?: BrowserImportDiscoveryOptions
|
|
35
|
+
): Promise<{ status: 'available' } | BrowserImportUnavailable>
|
|
36
|
+
export type BrowserImportTransfer = {
|
|
37
|
+
/** Contains a secret; never log or persist it. Browser policy may require a fresh user click. */
|
|
38
|
+
readonly url: string
|
|
39
|
+
/** Acknowledges receipt only, independently of destination selection and final import consent.
|
|
40
|
+
* Authenticated desktop cancellation throws code 'cancelled', terminal for this transfer.
|
|
41
|
+
* Keep the Blob and explicitly prepare a new transfer; never automatically retry cancellation. */
|
|
42
|
+
send(options?: {
|
|
43
|
+
signal?: AbortSignal
|
|
44
|
+
/** Waiting starts before readiness; transferring starts after identity verification, before bytes.
|
|
45
|
+
* No transferring event for an already-received package; cached receipts emit no events. */
|
|
46
|
+
onPhase?: (phase: BrowserImportPhase) => void
|
|
47
|
+
/** Acknowledged bytes only; first callback follows the first successful chunk acknowledgment. */
|
|
48
|
+
onProgress?: (progress: BrowserImportProgress) => void
|
|
49
|
+
/** Default attempts preserves the 1–5 attempt limit. wait-for-launch retries until the
|
|
50
|
+
* overall deadline or cancellation; it observes only this transfer's authenticated handshake. */
|
|
51
|
+
readinessMode?: BrowserImportReadinessMode
|
|
52
|
+
/** Readiness only; defaults to 120,000 ms. Capped at 120,000 ms in wait-for-launch,
|
|
53
|
+
* 300,000 ms in attempts mode. Timeout permits another send() on the same live transfer. */
|
|
54
|
+
readyTimeoutMs?: number
|
|
55
|
+
/** 1–5 attempts with 1,000 ms gaps; defaults to 5. Ignored in wait-for-launch mode. */
|
|
56
|
+
readinessAttempts?: number
|
|
57
|
+
/** Includes response body reads; defaults to 2,000 ms. Accepts up to 30,000 ms,
|
|
58
|
+
* clamped to 2,000 ms in wait-for-launch mode. Failed attempts have 1,000 ms gaps. */
|
|
59
|
+
attemptTimeoutMs?: number
|
|
60
|
+
}): Promise<{ received: true }>
|
|
61
|
+
}
|
|
62
|
+
/** Takes a complete browser-held Blob/File. Never fetches a remote package URL. */
|
|
63
|
+
export function prepareBrowserImport(
|
|
64
|
+
file: Blob,
|
|
65
|
+
options?: { filename?: string; origin?: string; port?: number }
|
|
66
|
+
): BrowserImportTransfer
|