@aipoch/open-science 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLI.md +745 -0
- package/LICENSE +201 -0
- package/README.md +197 -2
- package/appimage-launcher.mjs +90 -0
- package/browser-import.d.ts +66 -0
- package/browser-import.mjs +389 -0
- package/cli.mjs +1775 -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,198 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @aipoch/open-science
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Node.js SDK and command-line client for an Open-Science daemon running on the local machine.
|
|
4
|
+
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
- [CLI guide](./CLI.md) - installation, daemon lifecycle, task automation, artifacts, and exit codes
|
|
8
|
+
- [Release compatibility](https://github.com/aipoch/open-science/blob/main/docs/compatibility.md) - stable interfaces, versioning, and data upgrades
|
|
9
|
+
- [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
|
|
10
|
+
|
|
11
|
+
## Safety and integration
|
|
12
|
+
|
|
13
|
+
- **Scope:** Authenticated control of a local Open-Science service; the client does not provide an
|
|
14
|
+
independent execution sandbox or isolate the service's Agents and tools.
|
|
15
|
+
- **Controls:** Requests use the service's permission profiles, existing grants, and host-owned
|
|
16
|
+
runtime controls. Disabling interactive permission prompts denies requests needing a human; it
|
|
17
|
+
does not erase previously granted authority.
|
|
18
|
+
- **Remaining risks:** A successful automated run can modify allowed files, contact configured
|
|
19
|
+
services, or start remote jobs. A timeout or aborted wait stops observation, not the underlying run.
|
|
20
|
+
Research outputs still need scientific review.
|
|
21
|
+
- **Safe integration:** Protect local service credentials, review the effective profile/grants and
|
|
22
|
+
recipients, and use isolated test data before unattended operation. Cancel explicitly and confirm
|
|
23
|
+
the resulting task/job state. Inspect downloaded or exported artifacts before sharing.
|
|
24
|
+
|
|
25
|
+
## Browser-held package import
|
|
26
|
+
|
|
27
|
+
Use `@aipoch/open-science/browser-import` to send a downloaded `.science` Blob to the desktop app.
|
|
28
|
+
See the [browser integration guide](./BROWSER_IMPORT.md) for the click flow, API, security contract,
|
|
29
|
+
limits, and browser requirements. The desktop never downloads the online package URL.
|
|
30
|
+
|
|
31
|
+
## SDK quick start
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
import { connectToOpenScience } from '@aipoch/open-science'
|
|
35
|
+
|
|
36
|
+
const client = await connectToOpenScience()
|
|
37
|
+
const run = await client.startRun({
|
|
38
|
+
project: '<project-id-or-exact-name>',
|
|
39
|
+
prompt: 'Summarize the evidence.',
|
|
40
|
+
cwd: '/absolute/path/to/research',
|
|
41
|
+
permissionProfile: 'auto',
|
|
42
|
+
permissionPrompts: 'none' // Deny requests that would require a human; keep existing permission rules.
|
|
43
|
+
})
|
|
44
|
+
const result = await client.waitForRun(run.id)
|
|
45
|
+
console.log(result.output)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Session, Project-default, and global Agent-routing configuration use the same authenticated local
|
|
49
|
+
API:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
const configuration = await client.getSessionConfiguration(run.sessionId)
|
|
53
|
+
await client.updateSessionConfiguration(run.sessionId, {
|
|
54
|
+
expectedRevision: configuration.revision,
|
|
55
|
+
memoryEnabled: false
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const defaults = await client.getProjectSessionDefaults('systematic-review')
|
|
59
|
+
await client.updateProjectSessionDefaults('systematic-review', {
|
|
60
|
+
expectedUpdatedAt: defaults.updatedAt,
|
|
61
|
+
patch: { permissionProfile: 'auto' }
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
await client.updateAgentRouting({
|
|
65
|
+
framework: 'codex',
|
|
66
|
+
reviewer: { mode: 'inherit' },
|
|
67
|
+
subagent: { mode: 'inherit' }
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const runtimes = await client.listRuntimes()
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Session updates use `revision`; Project-default updates use `updatedAt`. Both reject stale writes.
|
|
74
|
+
Project defaults are copied only when a Session is created, with precedence `startRun` request,
|
|
75
|
+
Project defaults, application settings, then provider default. Agent-routing updates are atomic and
|
|
76
|
+
never return provider credentials.
|
|
77
|
+
|
|
78
|
+
Agent runtime listings expose only framework, readiness, version, and managed/external source. They
|
|
79
|
+
do not expose executable paths.
|
|
80
|
+
|
|
81
|
+
SDK requests have a 30-second default deadline that remains active while the response body is being
|
|
82
|
+
consumed. Override the client default with `requestTimeoutMs`, or pass `{ signal, timeoutMs }` as the
|
|
83
|
+
final options argument to an individual request method. `downloadArtifact` keeps the deadline active
|
|
84
|
+
while its returned `Response` body is streaming. `waitForRun` applies its overall `timeoutMs` and
|
|
85
|
+
caller signal to every in-flight polling request as well as the delay between polls. Each poll is
|
|
86
|
+
bounded by the smaller of the client's `requestTimeoutMs` and the remaining total wait budget.
|
|
87
|
+
A request timeout fails the wait immediately; it does not silently retry. Without a total
|
|
88
|
+
`timeoutMs`, waiting has no overall deadline. `pollIntervalMs` defaults to 250 and must be a finite
|
|
89
|
+
positive number.
|
|
90
|
+
|
|
91
|
+
Aborting or timing out a wait stops only the local observation. The Run may continue on the host;
|
|
92
|
+
call `getRun(run.id)` to check it, or explicitly call `cancelRun(run.id)` to stop it. With
|
|
93
|
+
`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.
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
const waitController = new AbortController()
|
|
97
|
+
const result = await client.waitForRun(run.id, {
|
|
98
|
+
timeoutMs: 120_000, // Total wait budget; each poll still has the client request deadline.
|
|
99
|
+
pollIntervalMs: 500,
|
|
100
|
+
returnOnAttention: true,
|
|
101
|
+
signal: waitController.signal // Aborts observation; does not call cancelRun.
|
|
102
|
+
})
|
|
103
|
+
console.log(result.status, result.attention)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
For retry-safe project creation and Run starts, pass the same `idempotencyKey` in the final options
|
|
107
|
+
argument on every attempt. The daemon replays the first response for up to 24 hours while it remains
|
|
108
|
+
running; reusing a key with a different request body fails with `idempotency_conflict`. If the
|
|
109
|
+
bounded replay registry is full, a new unique key fails with `idempotency_unavailable` rather than
|
|
110
|
+
evicting an existing guarantee. A lost response after submission does not prove that the Run was
|
|
111
|
+
not started. Retry with the original key and identical request while that daemon is still running,
|
|
112
|
+
or query the known Run ID. The replay registry is process-local: a daemon restart loses it, so
|
|
113
|
+
reusing a key after restart is not a durable exactly-once guarantee.
|
|
114
|
+
|
|
115
|
+
The `project` request field and the `listSessions(projectId)` argument both require a Project ID.
|
|
116
|
+
Project display names are not accepted as routing identifiers.
|
|
117
|
+
|
|
118
|
+
SDK and HTTP callers must supply an absolute `cwd`. Open-Science canonicalizes and validates it,
|
|
119
|
+
persists it as the Session working directory, and returns the effective path on every Run. Supplying
|
|
120
|
+
`cwd` with `sessionId` is allowed only when both paths resolve to the same directory. Omit `cwd` to
|
|
121
|
+
use a managed workspace. External working directories remain caller-owned and are never removed by
|
|
122
|
+
Open-Science.
|
|
123
|
+
|
|
124
|
+
For live automation feedback, subscribe before starting the Run. `run.progress` reports ordered
|
|
125
|
+
provider-neutral phases and emits a progress heartbeat every ten seconds until the first visible
|
|
126
|
+
provider output. This progress heartbeat describes Run activity; it is separate from the filtered
|
|
127
|
+
connection heartbeat that keeps an otherwise-idle WebSocket alive. The timer starts after Task has
|
|
128
|
+
prepared the Session and registered its Run; Session creation or resume time before registration is
|
|
129
|
+
outside this event stream:
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
const abortController = new AbortController()
|
|
133
|
+
const events = client.events({ signal: abortController.signal })
|
|
134
|
+
await events.ready
|
|
135
|
+
|
|
136
|
+
const progress = (async () => {
|
|
137
|
+
for await (const event of events) {
|
|
138
|
+
if (event.type === 'run.progress') {
|
|
139
|
+
console.log(event.data.phase, event.data.elapsedMs, event.data.heartbeat)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
})()
|
|
143
|
+
|
|
144
|
+
const observedRun = await client.startRun({
|
|
145
|
+
project: '<project-id-or-exact-name>',
|
|
146
|
+
prompt: 'Summarize the evidence.',
|
|
147
|
+
permissionProfile: 'auto'
|
|
148
|
+
})
|
|
149
|
+
const observedResult = await client.waitForRun(observedRun.id)
|
|
150
|
+
abortController.abort()
|
|
151
|
+
await progress
|
|
152
|
+
console.log(observedResult.output)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`events.ready` rejects if the socket fails, closes, or receives no liveness frame before opening.
|
|
156
|
+
After opening, the iterator fails with `timeout` when no event or connection heartbeat arrives for
|
|
157
|
+
30 seconds. Pass `idleTimeoutMs` to change that connection-liveness window; it does not limit model,
|
|
158
|
+
Notebook, permission-approval, or other Run duration. Connection heartbeats are control frames and
|
|
159
|
+
are not yielded to consumers. Malformed frames and a consumer backlog above 1024 events terminate
|
|
160
|
+
the iterator with `event_stream_invalid_message` or `event_stream_overflow` instead of throwing
|
|
161
|
+
outside the iterator or growing memory without a bound.
|
|
162
|
+
|
|
163
|
+
Every Run event includes top-level `sequence`, `runId`, `sessionId`, and `projectId` fields in
|
|
164
|
+
addition to its existing `type` and `data`. After an established socket closes unexpectedly, the SDK
|
|
165
|
+
reconnects with its last sequence and the daemon replays the retained suffix. Replay is bounded and
|
|
166
|
+
process-local. If the daemon restarted or the suffix was evicted, the iterator yields
|
|
167
|
+
`stream.resync-required` with reason `stream-changed` or `cursor-expired`; read the Run or Session
|
|
168
|
+
through the HTTP API to restore authoritative state.
|
|
169
|
+
|
|
170
|
+
Plan First runs can opt into actionable waiting with returnOnAttention. When the returned Run has
|
|
171
|
+
attention.kind equal to plan-approval, use getSessionPlan and respondSessionPlan. Calling waitForRun
|
|
172
|
+
without returnOnAttention keeps the original terminal-only behavior.
|
|
173
|
+
|
|
174
|
+
Automatic review and Specialist binding reuse existing Session JSON fields. Delegation adds
|
|
175
|
+
delegationPolicy with values allow or deny; an omitted historical value restores as allow.
|
|
176
|
+
|
|
177
|
+
To stop a still-running task instead of waiting for it, cancel it explicitly. Cancellation waits for
|
|
178
|
+
provider work and application finalization to drain before returning the terminal Run:
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
const cancelled = await client.cancelRun(run.id)
|
|
182
|
+
console.log(cancelled.status) // cancelled
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The client discovers the local daemon and reads its authentication token from the Open-Science config
|
|
186
|
+
directory. HTTP calls send the token in request headers. `events()` authenticates its WebSocket using
|
|
187
|
+
a `token` URL query parameter; treat WebSocket URLs and request logs as sensitive. Tokens are
|
|
188
|
+
not included in normal command output.
|
|
189
|
+
|
|
190
|
+
### Automation new-session defaults
|
|
191
|
+
|
|
192
|
+
`project session-defaults show|update` manages defaults for new Sessions created through the
|
|
193
|
+
Task CLI/API. Explicit run options override these defaults. Existing Sessions and desktop New
|
|
194
|
+
conversation drafts are not changed.
|
|
195
|
+
|
|
196
|
+
`--provider-default-model` keeps following the provider-owned default instead of pinning the
|
|
197
|
+
first model in its catalog. An explicit `--model` stays fixed. If a saved selection becomes
|
|
198
|
+
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
|