@researai/deepscientist 1.5.4 → 1.5.6

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/bin/ds.js CHANGED
@@ -36,7 +36,7 @@ const pythonCommands = new Set([
36
36
  const UPDATE_PACKAGE_NAME = String(packageJson.name || '@researai/deepscientist').trim() || '@researai/deepscientist';
37
37
  const UPDATE_CHECK_TTL_MS = 12 * 60 * 60 * 1000;
38
38
 
39
- const optionsWithValues = new Set(['--home', '--host', '--port', '--quest-id', '--mode']);
39
+ const optionsWithValues = new Set(['--home', '--host', '--port', '--quest-id', '--mode', '--proxy']);
40
40
 
41
41
  function printLauncherHelp() {
42
42
  console.log(`DeepScientist launcher
@@ -52,6 +52,7 @@ Usage:
52
52
  ds --tui
53
53
  ds --both
54
54
  ds --host 0.0.0.0 --port 21000
55
+ ds --host 0.0.0.0 --port 21000 --proxy http://127.0.0.1:58887
55
56
  ds --stop
56
57
  ds --restart
57
58
  ds --status
@@ -71,6 +72,7 @@ Launcher flags:
71
72
  --restart Restart the managed daemon
72
73
  --home <path> Use a custom DeepScientist home
73
74
  --here Use the current working directory as DeepScientist home
75
+ --proxy <url> Use an outbound HTTP/WS proxy for npm and Python runtime traffic
74
76
  --quest-id <id> Open the TUI on one quest directly
75
77
 
76
78
  Update:
@@ -115,6 +117,35 @@ function expandUserPath(rawPath) {
115
117
  return normalized;
116
118
  }
117
119
 
120
+ function normalizeProxyUrl(rawValue) {
121
+ const value = String(rawValue || '').trim();
122
+ return value || null;
123
+ }
124
+
125
+ function applyLauncherProxy(proxyUrl) {
126
+ const normalized = normalizeProxyUrl(proxyUrl);
127
+ if (!normalized) {
128
+ return null;
129
+ }
130
+ for (const key of ['HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'http_proxy', 'https_proxy', 'all_proxy']) {
131
+ process.env[key] = normalized;
132
+ }
133
+ for (const key of ['NO_PROXY', 'no_proxy']) {
134
+ const current = String(process.env[key] || '').trim();
135
+ const values = current
136
+ .split(',')
137
+ .map((item) => item.trim())
138
+ .filter(Boolean);
139
+ for (const host of ['127.0.0.1', 'localhost', '::1', '0.0.0.0']) {
140
+ if (!values.includes(host)) {
141
+ values.push(host);
142
+ }
143
+ }
144
+ process.env[key] = values.join(',');
145
+ }
146
+ return normalized;
147
+ }
148
+
118
149
  function updateStatePath(home) {
119
150
  return path.join(home, 'runtime', 'update-state.json');
120
151
  }
@@ -842,6 +873,7 @@ function parseLauncherArgs(argv) {
842
873
  let host = null;
843
874
  let port = null;
844
875
  let home = null;
876
+ let proxy = null;
845
877
  let stop = false;
846
878
  let restart = false;
847
879
  let openBrowser = null;
@@ -870,6 +902,7 @@ function parseLauncherArgs(argv) {
870
902
  else if (arg === '--host' && args[index + 1]) host = args[++index];
871
903
  else if (arg === '--port' && args[index + 1]) port = Number(args[++index]);
872
904
  else if (arg === '--home' && args[index + 1]) home = path.resolve(args[++index]);
905
+ else if (arg === '--proxy' && args[index + 1]) proxy = args[++index];
873
906
  else if (arg === '--quest-id' && args[index + 1]) questId = args[++index];
874
907
  else if (arg === '--mode' && args[index + 1]) mode = normalizeMode(args[++index]);
875
908
  else if (arg === '--help' || arg === '-h') return { help: true };
@@ -882,6 +915,7 @@ function parseLauncherArgs(argv) {
882
915
  host,
883
916
  port,
884
917
  home,
918
+ proxy,
885
919
  stop,
886
920
  restart,
887
921
  status,
@@ -944,6 +978,7 @@ function parseUpdateArgs(argv) {
944
978
  let home = null;
945
979
  let host = null;
946
980
  let port = null;
981
+ let proxy = null;
947
982
  let restartDaemon = null;
948
983
  let skipUpdateCheck = false;
949
984
 
@@ -962,6 +997,7 @@ function parseUpdateArgs(argv) {
962
997
  else if (arg === '--home' && args[index + 1]) home = path.resolve(args[++index]);
963
998
  else if (arg === '--host' && args[index + 1]) host = args[++index];
964
999
  else if (arg === '--port' && args[index + 1]) port = Number(args[++index]);
1000
+ else if (arg === '--proxy' && args[index + 1]) proxy = args[++index];
965
1001
  else if (arg === '--help' || arg === '-h') return { help: true };
966
1002
  else if (!arg.startsWith('--')) return null;
967
1003
  }
@@ -979,6 +1015,7 @@ function parseUpdateArgs(argv) {
979
1015
  home,
980
1016
  host,
981
1017
  port,
1018
+ proxy,
982
1019
  restartDaemon,
983
1020
  skipUpdateCheck,
984
1021
  };
@@ -2819,7 +2856,7 @@ function readConfiguredUiAddressFromFile(home, fallbackHost, fallbackPort) {
2819
2856
  }
2820
2857
  }
2821
2858
 
2822
- async function startDaemon(home, runtimePython, host, port) {
2859
+ async function startDaemon(home, runtimePython, host, port, proxy = null) {
2823
2860
  const browserUrl = browserUiUrl(host, port);
2824
2861
  const daemonBindUrl = bindUiUrl(host, port);
2825
2862
  const state = readDaemonState(home);
@@ -2860,7 +2897,18 @@ async function startDaemon(home, runtimePython, host, port) {
2860
2897
  const daemonId = crypto.randomUUID();
2861
2898
  const child = spawn(
2862
2899
  runtimePython,
2863
- ['-m', 'deepscientist.cli', '--home', home, 'daemon', '--host', host, '--port', String(port)],
2900
+ [
2901
+ '-m',
2902
+ 'deepscientist.cli',
2903
+ '--home',
2904
+ home,
2905
+ ...(normalizeProxyUrl(proxy) ? ['--proxy', normalizeProxyUrl(proxy)] : []),
2906
+ 'daemon',
2907
+ '--host',
2908
+ host,
2909
+ '--port',
2910
+ String(port),
2911
+ ],
2864
2912
  {
2865
2913
  cwd: repoRoot,
2866
2914
  detached: true,
@@ -2998,6 +3046,7 @@ async function updateMain(rawArgs) {
2998
3046
  }
2999
3047
 
3000
3048
  const home = options.home || resolveHome(rawArgs);
3049
+ applyLauncherProxy(options.proxy);
3001
3050
  ensureDir(home);
3002
3051
 
3003
3052
  if (options.background && options.yes && !options.worker) {
@@ -3225,6 +3274,7 @@ async function launcherMain(rawArgs) {
3225
3274
  }
3226
3275
 
3227
3276
  const home = options.home || resolveHome(rawArgs);
3277
+ applyLauncherProxy(options.proxy);
3228
3278
  ensureDir(home);
3229
3279
 
3230
3280
  if (options.stop) {
@@ -3281,7 +3331,7 @@ async function launcherMain(rawArgs) {
3281
3331
  step(4, 4, 'Starting local daemon and UI surfaces');
3282
3332
  let started;
3283
3333
  try {
3284
- started = await startDaemon(home, runtimePython, host, port);
3334
+ started = await startDaemon(home, runtimePython, host, port, options.proxy);
3285
3335
  } catch (error) {
3286
3336
  if (handleCodexPreflightFailure(error)) return true;
3287
3337
  throw error;
@@ -3366,6 +3416,8 @@ module.exports = {
3366
3416
  legacyVenvRootPath,
3367
3417
  resolveUvBinary,
3368
3418
  resolveHome,
3419
+ parseLauncherArgs,
3420
+ normalizeProxyUrl,
3369
3421
  parseMigrateArgs,
3370
3422
  useEditableProjectInstall,
3371
3423
  compareVersions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@researai/deepscientist",
3
- "version": "1.5.4",
3
+ "version": "1.5.6",
4
4
  "description": "Local-first research operating system with a Python runtime and npm launcher",
5
5
  "license": "MIT",
6
6
  "files": [
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deepscientist"
7
- version = "1.5.4"
7
+ version = "1.5.6"
8
8
  description = "DeepScientist Core skeleton"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -5,4 +5,4 @@ __all__ = ["__version__"]
5
5
  try:
6
6
  __version__ = _package_version("deepscientist")
7
7
  except PackageNotFoundError: # pragma: no cover - source checkout fallback
8
- __version__ = "1.5.4"
8
+ __version__ = "1.5.6"
@@ -745,7 +745,7 @@ class PromptBuilder:
745
745
  "- response_pattern: say what changed -> say what it means -> say what happens next",
746
746
  "- interaction_protocol: first message may be plain conversation; after that, treat artifact.interact threads and mailbox polls as the main continuity spine across TUI, web, and connectors",
747
747
  "- mailbox_protocol: artifact.interact(include_recent_inbound_messages=True) is the queued human-message mailbox; when it returns user text, treat that input as higher priority than background subtasks until it has been acknowledged",
748
- "- acknowledgment_protocol: after artifact.interact returns any human message, immediately call artifact.interact(...) again to confirm receipt; if answerable, answer directly, otherwise state the short plan, nearest checkpoint, and that the current background subtask is paused",
748
+ "- acknowledgment_protocol: after artifact.interact returns any human message, immediately send one substantive artifact.interact(...) follow-up; if the active connector runtime already emitted a transport-level receipt acknowledgement, do not send a redundant receipt-only message; if answerable, answer directly, otherwise state the short plan, nearest checkpoint, and that the current background subtask is paused",
749
749
  "- progress_protocol: emit artifact.interact(kind='progress', reply_mode='threaded', ...) at real human-meaningful checkpoints; if no natural checkpoint appears during active user-relevant work, send a concise keepalive before you drift beyond roughly 10 to 30 tool calls without a user-visible update",
750
750
  "- smoke_then_detach_protocol: for baseline reproduction, main experiments, and analysis experiments, first validate the command path with a bounded smoke test; once the smoke test passes, launch the real long run with bash_exec(mode='detach', ...) and usually leave timeout_seconds unset rather than guessing a fake deadline",
751
751
  "- progress_first_monitoring_protocol: when supervising a long-running bash_exec session, judge health by forward progress rather than by whether the final artifact has already appeared within a short window",
@@ -2813,8 +2813,8 @@ class QuestService:
2813
2813
  ),
2814
2814
  self.localized_copy(
2815
2815
  quest_root=quest_root,
2816
- zh="- 立即再调用一次 artifact.interact(...),明确告知你已经收到这些用户消息。",
2817
- en="- Immediately call artifact.interact(...) again to confirm that you received these user messages.",
2816
+ zh="- 立即发送一条有实际内容的 follow-up artifact.interact(...);如果当前 connector 的运行时已经替你发过即时回执,就不要再重复发送一条只有“已收到/处理中”的确认。",
2817
+ en="- Immediately send one substantive follow-up artifact.interact(...); if the active connector runtime already sent the transport-level receipt acknowledgement, do not send a redundant receipt-only message such as 'received' or 'processing'.",
2818
2818
  ),
2819
2819
  self.localized_copy(
2820
2820
  quest_root=quest_root,
@@ -3,6 +3,8 @@
3
3
  - connector_contract_id: qq
4
4
  - connector_contract_scope: loaded only when QQ is the active or bound external connector for this quest
5
5
  - connector_contract_goal: use `artifact.interact(...)` as the main durable user-visible thread on QQ instead of exposing raw internal runner or tool chatter
6
+ - qq_runtime_ack_rule: the QQ bridge itself emits the immediate transport-level receipt acknowledgement before the model turn starts
7
+ - qq_no_duplicate_ack_rule: do not waste your first model response or first `artifact.interact(...)` call on a redundant receipt-only acknowledgement such as "received", "已收到", or "I am processing" when the bridge already sent that
6
8
  - qq_reply_style: keep QQ replies concise, milestone-first, respectful, and easy to scan on a phone
7
9
  - qq_reply_length_rule: for ordinary QQ progress updates, normally use only 2 to 4 short sentences, or 3 short bullets at most
8
10
  - qq_summary_first_rule: start with the conclusion the user cares about, then what it means, then the next action
@@ -16,6 +18,7 @@
16
18
  - qq_default_text_rule: plain text is the default and safest QQ mode
17
19
  - qq_absolute_path_rule: when you request native QQ image or file delivery via an attachment `path`, prefer an absolute path
18
20
  - qq_failure_rule: if `artifact.interact(...)` returns `attachment_issues` or `delivery_results` errors, treat that as a real delivery failure and adapt before assuming the user received the media
21
+ - qq_first_followup_rule: after a new inbound QQ message, your first substantive follow-up should either answer directly or give the first meaningful checkpoint and next action, not a second bare acknowledgement
19
22
 
20
23
  ## QQ Runtime Capabilities
21
24
 
@@ -1027,7 +1027,8 @@ For `artifact.interact(...)` specifically:
1027
1027
  - ordinary user-facing progress updates should read like a short collaborator message, not like a monitoring transcript, execution diary, or internal postmortem
1028
1028
  - when `artifact.interact(...)` returns queued user requirements, treat that mailbox payload as the latest user instruction bundle
1029
1029
  - if queued user requirements were returned, treat them as higher priority than the current background subtask until you have acknowledged them
1030
- - immediately follow a non-empty mailbox poll with another `artifact.interact(...)` update that confirms receipt
1030
+ - immediately follow a non-empty mailbox poll with one substantive `artifact.interact(...)` follow-up update
1031
+ - if the active connector runtime already emitted a transport-level receipt acknowledgement before your turn, do not send a redundant receipt-only update such as "received" or "processing"
1031
1032
  - if the request is directly answerable, answer it in that immediate follow-up update
1032
1033
  - otherwise say the current subtask is being paused, give a short execution plan plus nearest report-back point, then complete the user request first
1033
1034
  - after completing that interrupting user request, send another `artifact.interact(...)` update with the full result before resuming older work
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepscientist-tui",
3
- "version": "1.5.4",
3
+ "version": "1.5.6",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "main": "dist/index.js",