ldcon 0.2.0__py3-none-any.whl

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.
Files changed (482) hide show
  1. ldcon/__init__.py +302 -0
  2. ldcon/_moved.py +49 -0
  3. ldcon/agent_tools.py +4 -0
  4. ldcon/agents/__init__.py +1 -0
  5. ldcon/agents/adjustment_guidance.py +73 -0
  6. ldcon/agents/adjustment_quality.py +399 -0
  7. ldcon/agents/agent_adapters.py +709 -0
  8. ldcon/agents/agent_background.py +307 -0
  9. ldcon/agents/agent_bundle.py +75 -0
  10. ldcon/agents/agent_cards.py +171 -0
  11. ldcon/agents/agent_config_access.py +22 -0
  12. ldcon/agents/agent_configuration.py +224 -0
  13. ldcon/agents/agent_configuration_policy.py +67 -0
  14. ldcon/agents/agent_connect.py +93 -0
  15. ldcon/agents/agent_device_parameters.py +41 -0
  16. ldcon/agents/agent_execution.py +77 -0
  17. ldcon/agents/agent_history.py +112 -0
  18. ldcon/agents/agent_inbox_watch.py +121 -0
  19. ldcon/agents/agent_interactive.py +386 -0
  20. ldcon/agents/agent_interactive_hooks.py +381 -0
  21. ldcon/agents/agent_language.py +48 -0
  22. ldcon/agents/agent_log_context.py +308 -0
  23. ldcon/agents/agent_log_read.py +250 -0
  24. ldcon/agents/agent_log_watch.py +27 -0
  25. ldcon/agents/agent_metrics.py +111 -0
  26. ldcon/agents/agent_monitor.py +272 -0
  27. ldcon/agents/agent_opencode_http.py +270 -0
  28. ldcon/agents/agent_opencode_mcp.py +64 -0
  29. ldcon/agents/agent_owner_status.py +216 -0
  30. ldcon/agents/agent_profiles.py +186 -0
  31. ldcon/agents/agent_providers.py +53 -0
  32. ldcon/agents/agent_repairs.py +105 -0
  33. ldcon/agents/agent_routing.py +90 -0
  34. ldcon/agents/agent_runtime.py +70 -0
  35. ldcon/agents/agent_sandbox.py +141 -0
  36. ldcon/agents/agent_startup_notice.py +142 -0
  37. ldcon/agents/agent_task.py +336 -0
  38. ldcon/agents/agent_tools.py +488 -0
  39. ldcon/agents/agent_window.py +70 -0
  40. ldcon/agents/agent_workspace.py +340 -0
  41. ldcon/agents/card_fallback.py +284 -0
  42. ldcon/agents/dlc_pi_adjustment.py +250 -0
  43. ldcon/agents/dlc_pi_plan.py +24 -0
  44. ldcon/agents/dlc_pi_response_plan.py +26 -0
  45. ldcon/agents/failure_policy.py +473 -0
  46. ldcon/agents/fallback_service.py +146 -0
  47. ldcon/agents/fallback_targets.py +96 -0
  48. ldcon/agents/gateway_role.py +31 -0
  49. ldcon/agents/manual_adjustment_grant.py +66 -0
  50. ldcon/agents/measurement_grant.py +95 -0
  51. ldcon/agents/monitor_quality_binding.py +33 -0
  52. ldcon/agents/notification_policy.py +346 -0
  53. ldcon/agents/operating_mode_client.py +99 -0
  54. ldcon/agents/report_pdf.py +105 -0
  55. ldcon/agents/resident_adjustment.py +572 -0
  56. ldcon/agents/resident_agent.py +438 -0
  57. ldcon/agents/resident_quality_apply.py +233 -0
  58. ldcon/agents/task_decisions.py +218 -0
  59. ldcon/agents/teams_card.py +100 -0
  60. ldcon/agents/teams_destinations.py +157 -0
  61. ldcon/cld_owner.py +4 -0
  62. ldcon/cli.py +19 -0
  63. ldcon/core/__init__.py +1 -0
  64. ldcon/core/adjustment_plan.py +47 -0
  65. ldcon/core/adjustment_registry.py +100 -0
  66. ldcon/core/agent_authority.py +903 -0
  67. ldcon/core/card_admission.py +15 -0
  68. ldcon/core/card_definition.py +14 -0
  69. ldcon/core/card_reporting.py +238 -0
  70. ldcon/core/configuration.py +357 -0
  71. ldcon/core/configuration_transaction.py +225 -0
  72. ldcon/core/contracts.py +86 -0
  73. ldcon/core/current_settings.py +86 -0
  74. ldcon/core/deck_yaml.py +45 -0
  75. ldcon/core/device_conditions.py +133 -0
  76. ldcon/core/execution_context.py +3 -0
  77. ldcon/core/instrument_client.py +190 -0
  78. ldcon/core/interface_cards.py +520 -0
  79. ldcon/core/interface_schema.py +640 -0
  80. ldcon/core/laser_assets.py +238 -0
  81. ldcon/core/metric_quantities.py +38 -0
  82. ldcon/core/modulation.py +107 -0
  83. ldcon/core/operation_registry.py +195 -0
  84. ldcon/core/paths.py +26 -0
  85. ldcon/core/period_context.py +118 -0
  86. ldcon/core/records.py +4 -0
  87. ldcon/core/result_reservations.py +469 -0
  88. ldcon/core/site_bindings.py +25 -0
  89. ldcon/core/state_layout.py +70 -0
  90. ldcon/core/target_policy.py +79 -0
  91. ldcon/core/telegraf.py +106 -0
  92. ldcon/core/telemetry.py +130 -0
  93. ldcon/core/temperature_settings.py +12 -0
  94. ldcon/dashboard_metrics.py +4 -0
  95. ldcon/device_registry.py +4 -0
  96. ldcon/drivers/__init__.py +1 -0
  97. ldcon/drivers/cld1015.py +296 -0
  98. ldcon/drivers/cld1015_config.py +31 -0
  99. ldcon/drivers/dlcpro.py +177 -0
  100. ldcon/drivers/dlcpro_analog.py +33 -0
  101. ldcon/drivers/dlcpro_console.py +133 -0
  102. ldcon/drivers/dlcpro_priority.py +46 -0
  103. ldcon/drivers/ecdl.py +334 -0
  104. ldcon/drivers/esp32_servo.py +187 -0
  105. ldcon/drivers/fizeau.py +114 -0
  106. ldcon/drivers/frequency_sweep.py +69 -0
  107. ldcon/drivers/influx_wavelength.py +70 -0
  108. ldcon/drivers/metric_sources.py +82 -0
  109. ldcon/drivers/native_dither_telemetry.py +50 -0
  110. ldcon/drivers/rigol_scope.py +126 -0
  111. ldcon/drivers/siglent.py +418 -0
  112. ldcon/drivers/staircase.py +155 -0
  113. ldcon/drivers/wsu_udp.py +164 -0
  114. ldcon/esp32_time_telemetry.py +4 -0
  115. ldcon/example_config.py +41 -0
  116. ldcon/fallback_service.py +4 -0
  117. ldcon/instrument_owner.py +4 -0
  118. ldcon/instrument_service.py +4 -0
  119. ldcon/interface_worker.py +4 -0
  120. ldcon/job_service.py +4 -0
  121. ldcon/jobs/__init__.py +1 -0
  122. ldcon/jobs/adaptive_pid.py +234 -0
  123. ldcon/jobs/adaptive_window_session.py +922 -0
  124. ldcon/jobs/adjustment_continuation.py +284 -0
  125. ldcon/jobs/axis_frequency.py +219 -0
  126. ldcon/jobs/axis_frequency_report.py +86 -0
  127. ldcon/jobs/background_parameter_session.py +142 -0
  128. ldcon/jobs/background_pid_session.py +447 -0
  129. ldcon/jobs/branch_maps.py +71 -0
  130. ldcon/jobs/branch_target_migration.py +33 -0
  131. ldcon/jobs/card_runtime.py +157 -0
  132. ldcon/jobs/change_measure.py +293 -0
  133. ldcon/jobs/change_measure_adapter.py +360 -0
  134. ldcon/jobs/change_measure_dlc.py +94 -0
  135. ldcon/jobs/change_measure_parameters.py +222 -0
  136. ldcon/jobs/change_measure_target.py +135 -0
  137. ldcon/jobs/current_cards.py +159 -0
  138. ldcon/jobs/current_devices.py +71 -0
  139. ldcon/jobs/dashboard_api.py +61 -0
  140. ldcon/jobs/device_parameters.py +426 -0
  141. ldcon/jobs/dlc_pi_measurement.py +151 -0
  142. ldcon/jobs/dlcpro_awg_analysis.py +77 -0
  143. ldcon/jobs/dlcpro_awg_card.py +116 -0
  144. ldcon/jobs/dlcpro_cards.py +240 -0
  145. ldcon/jobs/ecdl_branch_hop.py +175 -0
  146. ldcon/jobs/ecdl_branch_recovery.py +176 -0
  147. ldcon/jobs/ecdl_devices.py +306 -0
  148. ldcon/jobs/ecdl_locked_sweep_proxy.py +526 -0
  149. ldcon/jobs/ecdl_mapped_recovery.py +6 -0
  150. ldcon/jobs/ecdl_mode_map.py +469 -0
  151. ldcon/jobs/ecdl_mode_recovery.py +20 -0
  152. ldcon/jobs/ecdl_online.py +158 -0
  153. ldcon/jobs/ecdl_operating_point.py +403 -0
  154. ldcon/jobs/ecdl_pi_commission.py +161 -0
  155. ldcon/jobs/ecdl_recovery.py +25 -0
  156. ldcon/jobs/ecdl_recovery_plan.py +90 -0
  157. ldcon/jobs/ecdl_recovery_test.py +59 -0
  158. ldcon/jobs/ecdl_relock.py +7 -0
  159. ldcon/jobs/ecdl_response_study.py +369 -0
  160. ldcon/jobs/ecdl_steady.py +343 -0
  161. ldcon/jobs/ecdl_thermal_context.py +242 -0
  162. ldcon/jobs/ecdl_window_center.py +734 -0
  163. ldcon/jobs/emission_card.py +102 -0
  164. ldcon/jobs/fringe_sweep_reference.py +89 -0
  165. ldcon/jobs/interface_devices.py +122 -0
  166. ldcon/jobs/interface_worker.py +471 -0
  167. ldcon/jobs/job_service.py +125 -0
  168. ldcon/jobs/laser_live.py +270 -0
  169. ldcon/jobs/learned_settling.py +450 -0
  170. ldcon/jobs/lock_target.py +461 -0
  171. ldcon/jobs/locked_triangle.py +36 -0
  172. ldcon/jobs/measurement_acquisition.py +51 -0
  173. ldcon/jobs/measurement_analysis.py +59 -0
  174. ldcon/jobs/measurement_execution.py +40 -0
  175. ldcon/jobs/measurement_notice.py +78 -0
  176. ldcon/jobs/measurement_presets.py +52 -0
  177. ldcon/jobs/measurement_report.py +76 -0
  178. ldcon/jobs/measurement_statistics.py +22 -0
  179. ldcon/jobs/metric_cards.py +34 -0
  180. ldcon/jobs/metric_interface.py +100 -0
  181. ldcon/jobs/modulation_sweep.py +274 -0
  182. ldcon/jobs/native_background_session.py +91 -0
  183. ldcon/jobs/native_current_acquire.py +143 -0
  184. ldcon/jobs/native_current_card.py +328 -0
  185. ldcon/jobs/native_current_online.py +129 -0
  186. ldcon/jobs/native_ff_proxy.py +1361 -0
  187. ldcon/jobs/native_ff_sweep.py +802 -0
  188. ldcon/jobs/online_pid.py +178 -0
  189. ldcon/jobs/operating_point.py +1399 -0
  190. ldcon/jobs/operating_point_proxy.py +236 -0
  191. ldcon/jobs/operation_execution.py +126 -0
  192. ldcon/jobs/operation_history.py +419 -0
  193. ldcon/jobs/optical_broker.py +23 -0
  194. ldcon/jobs/pi_algorithms.py +30 -0
  195. ldcon/jobs/pi_response.py +239 -0
  196. ldcon/jobs/progress.py +63 -0
  197. ldcon/jobs/quality_baseline.py +411 -0
  198. ldcon/jobs/recovery_decisions.py +131 -0
  199. ldcon/jobs/recovery_history.py +152 -0
  200. ldcon/jobs/recovery_procedure.py +285 -0
  201. ldcon/jobs/recovery_reference.py +123 -0
  202. ldcon/jobs/recovery_stationary.py +138 -0
  203. ldcon/jobs/response_algorithms.py +47 -0
  204. ldcon/jobs/response_comparison.py +62 -0
  205. ldcon/jobs/response_record.py +90 -0
  206. ldcon/jobs/runtime_parameters.py +434 -0
  207. ldcon/jobs/settling_profile.py +140 -0
  208. ldcon/jobs/shot_analysis.py +1300 -0
  209. ldcon/jobs/simulation.py +127 -0
  210. ldcon/jobs/slow_control.py +208 -0
  211. ldcon/jobs/slow_current.py +88 -0
  212. ldcon/jobs/study_adoption.py +122 -0
  213. ldcon/jobs/study_execution.py +182 -0
  214. ldcon/jobs/sweep_ff_analysis.py +320 -0
  215. ldcon/jobs/sweep_ff_optimization.py +202 -0
  216. ldcon/jobs/sweep_ff_plan.py +166 -0
  217. ldcon/jobs/sweep_ff_report.py +254 -0
  218. ldcon/jobs/sweep_ff_transport.py +326 -0
  219. ldcon/jobs/sweep_tracking.py +424 -0
  220. ldcon/jobs/sweep_verdict.py +288 -0
  221. ldcon/jobs/target_response.py +638 -0
  222. ldcon/jobs/target_response_analysis.py +65 -0
  223. ldcon/jobs/target_response_control.py +215 -0
  224. ldcon/jobs/target_response_decisions.py +152 -0
  225. ldcon/jobs/target_response_experiment.py +110 -0
  226. ldcon/jobs/target_response_feedforward.py +130 -0
  227. ldcon/jobs/target_response_search.py +225 -0
  228. ldcon/jobs/target_response_shift.py +475 -0
  229. ldcon/jobs/target_response_thermal.py +113 -0
  230. ldcon/jobs/target_response_thermal_experiment.py +120 -0
  231. ldcon/jobs/target_sequence.py +166 -0
  232. ldcon/jobs/temperature_identification.py +86 -0
  233. ldcon/jobs/wavelength_history.py +17 -0
  234. ldcon/jobs/window_actions.py +641 -0
  235. ldcon/jobs/window_center.py +784 -0
  236. ldcon/jobs/window_control.py +130 -0
  237. ldcon/jobs/window_decisions.py +105 -0
  238. ldcon/jobs/window_sampling.py +66 -0
  239. ldcon/jobs/workflow_artifacts.py +73 -0
  240. ldcon/migration/__init__.py +810 -0
  241. ldcon/migration/migration_decks.py +239 -0
  242. ldcon/migration/migration_readiness.py +397 -0
  243. ldcon/migration/migration_relocation.py +283 -0
  244. ldcon/monitor_owner.py +4 -0
  245. ldcon/owner_telemetry.py +4 -0
  246. ldcon/owners/__init__.py +1 -0
  247. ldcon/owners/adc_log_threshold.py +209 -0
  248. ldcon/owners/background_grants.py +44 -0
  249. ldcon/owners/cld_instrument_owner.py +96 -0
  250. ldcon/owners/cld_owner.py +706 -0
  251. ldcon/owners/cld_projection.py +194 -0
  252. ldcon/owners/cld_system_owner.py +276 -0
  253. ldcon/owners/current_owner.py +68 -0
  254. ldcon/owners/device_registry.py +229 -0
  255. ldcon/owners/dlcpro_awg_plan.py +115 -0
  256. ldcon/owners/dlcpro_awg_port.py +98 -0
  257. ldcon/owners/dlcpro_awg_protocol.py +190 -0
  258. ldcon/owners/dlcpro_awg_return.py +63 -0
  259. ldcon/owners/dlcpro_awg_sweep.py +437 -0
  260. ldcon/owners/dlcpro_awg_timing.py +35 -0
  261. ldcon/owners/dlcpro_awg_tracking.py +175 -0
  262. ldcon/owners/dlcpro_awg_tracking_guard.py +87 -0
  263. ldcon/owners/dlcpro_awg_tracking_report.py +49 -0
  264. ldcon/owners/dlcpro_awg_zero.py +82 -0
  265. ldcon/owners/dlcpro_deployment.py +66 -0
  266. ldcon/owners/dlcpro_ff.py +64 -0
  267. ldcon/owners/dlcpro_ff_adjustment.py +159 -0
  268. ldcon/owners/dlcpro_ff_delta_adjustment.py +194 -0
  269. ldcon/owners/dlcpro_frequency_window.py +61 -0
  270. ldcon/owners/dlcpro_observation.py +62 -0
  271. ldcon/owners/dlcpro_owner.py +830 -0
  272. ldcon/owners/dlcpro_pi_protocol.py +44 -0
  273. ldcon/owners/dlcpro_pi_trial.py +161 -0
  274. ldcon/owners/dlcpro_readback.py +50 -0
  275. ldcon/owners/dlcpro_service.py +203 -0
  276. ldcon/owners/dlcpro_target_changes.py +330 -0
  277. ldcon/owners/dlcpro_target_step.py +44 -0
  278. ldcon/owners/dlcpro_target_store.py +75 -0
  279. ldcon/owners/dlcpro_telemetry.py +72 -0
  280. ldcon/owners/ecdl_background_parameters.py +248 -0
  281. ldcon/owners/ecdl_background_pid.py +184 -0
  282. ldcon/owners/ecdl_hot_control.py +255 -0
  283. ldcon/owners/ecdl_independent_pid.py +95 -0
  284. ldcon/owners/ecdl_locked_study.py +122 -0
  285. ldcon/owners/ecdl_locked_sweep.py +1875 -0
  286. ldcon/owners/ecdl_native_pid.py +181 -0
  287. ldcon/owners/ecdl_optical.py +37 -0
  288. ldcon/owners/ecdl_owner.py +1058 -0
  289. ldcon/owners/ecdl_policy_reload.py +494 -0
  290. ldcon/owners/ecdl_prelock.py +612 -0
  291. ldcon/owners/ecdl_projection.py +636 -0
  292. ldcon/owners/ecdl_steady_owner.py +1859 -0
  293. ldcon/owners/ecdl_steady_policy.py +212 -0
  294. ldcon/owners/ecdl_target_ff.py +214 -0
  295. ldcon/owners/ecdl_target_settling.py +163 -0
  296. ldcon/owners/ecdl_tec_pair.py +88 -0
  297. ldcon/owners/ecdl_thermal_study.py +251 -0
  298. ldcon/owners/emission.py +365 -0
  299. ldcon/owners/emission_adapters.py +268 -0
  300. ldcon/owners/esp32_native_client.py +335 -0
  301. ldcon/owners/esp32_native_owner.py +2166 -0
  302. ldcon/owners/esp32_time_telemetry.py +253 -0
  303. ldcon/owners/esp32_udp_gate.py +136 -0
  304. ldcon/owners/external_sweep_guard.py +44 -0
  305. ldcon/owners/flexible_native_trial.py +172 -0
  306. ldcon/owners/fringe_peaks.py +92 -0
  307. ldcon/owners/full_data_log.py +133 -0
  308. ldcon/owners/instrument_identity.py +125 -0
  309. ldcon/owners/instrument_owner.py +1041 -0
  310. ldcon/owners/instrument_projection.py +104 -0
  311. ldcon/owners/instrument_service.py +638 -0
  312. ldcon/owners/migration_physical_lock.py +162 -0
  313. ldcon/owners/monitor_owner.py +322 -0
  314. ldcon/owners/native_assembly.py +37 -0
  315. ldcon/owners/native_assembly_host.py +260 -0
  316. ldcon/owners/native_background.py +233 -0
  317. ldcon/owners/native_current_tuning.py +251 -0
  318. ldcon/owners/native_ff_proof.py +433 -0
  319. ldcon/owners/native_instrument_owner.py +161 -0
  320. ldcon/owners/native_modulation_limits.py +45 -0
  321. ldcon/owners/native_policy_reload.py +186 -0
  322. ldcon/owners/native_policy_safety.py +213 -0
  323. ldcon/owners/native_policy_update.py +473 -0
  324. ldcon/owners/native_system_owner.py +420 -0
  325. ldcon/owners/operating_mode_owner.py +128 -0
  326. ldcon/owners/owner_projection.py +530 -0
  327. ldcon/owners/owner_telemetry.py +166 -0
  328. ldcon/owners/pi_handover.py +154 -0
  329. ldcon/owners/projection_transport.py +143 -0
  330. ldcon/owners/remote_instruments.py +323 -0
  331. ldcon/owners/sdg_client.py +122 -0
  332. ldcon/owners/sdg_owner.py +468 -0
  333. ldcon/owners/tec_rate.py +29 -0
  334. ldcon/owners/wsu_udp_monitor.py +113 -0
  335. ldcon/report_pdf.py +4 -0
  336. ldcon/resources/agent-model-options.json +142 -0
  337. ldcon/resources/agent_contract/adjustment.txt +45 -0
  338. ldcon/resources/agent_contract/common.txt +42 -0
  339. ldcon/resources/agent_contract/diagnosis.txt +11 -0
  340. ldcon/resources/agent_contract/research.txt +20 -0
  341. ldcon/resources/agent_contract/response_optimization.txt +13 -0
  342. ldcon/resources/examples/README.html +10 -0
  343. ldcon/resources/examples/assignments/sr1092.yaml +100 -0
  344. ldcon/resources/examples/assignments/sr422.yaml +677 -0
  345. ldcon/resources/examples/assignments/thf685.yaml +7 -0
  346. ldcon/resources/examples/interface-decks/dfb-dbr-ldc-tec.yaml +430 -0
  347. ldcon/resources/examples/interface-decks/ecdl-dlcpro.yaml +288 -0
  348. ldcon/resources/examples/interface-decks/ecdl-pzt-ldc-tec.yaml +698 -0
  349. ldcon/resources/examples/lasers/sr1092.yaml +267 -0
  350. ldcon/resources/examples/lasers/sr422.yaml +403 -0
  351. ldcon/resources/examples/lasers/thf685.yaml +158 -0
  352. ldcon/resources/report_template/ldconreport.cls +42 -0
  353. ldcon/resources/report_template/report.tex +47 -0
  354. ldcon/web/__init__.py +1 -0
  355. ldcon/web/agent_approval_api.py +126 -0
  356. ldcon/web/agent_settings_api.py +54 -0
  357. ldcon/web/agent_task_api.py +211 -0
  358. ldcon/web/control_readback.py +63 -0
  359. ldcon/web/dashboard_activity.py +109 -0
  360. ldcon/web/dashboard_controls_revision.py +29 -0
  361. ldcon/web/dashboard_metrics.py +146 -0
  362. ldcon/web/dashboard_snapshot.py +163 -0
  363. ldcon/web/dashboard_state.py +104 -0
  364. ldcon/web/dashboard_wavelength.py +150 -0
  365. ldcon/web/device_parameters_api.py +52 -0
  366. ldcon/web/dither_quality.py +46 -0
  367. ldcon/web/dlcpro_target_api.py +91 -0
  368. ldcon/web/dlcpro_target_query.py +23 -0
  369. ldcon/web/failure_api.py +62 -0
  370. ldcon/web/gateway_lan.py +208 -0
  371. ldcon/web/grafana_activity.py +84 -0
  372. ldcon/web/grafana_dashboard.py +492 -0
  373. ldcon/web/grafana_dlcpro.py +158 -0
  374. ldcon/web/grafana_quality.py +167 -0
  375. ldcon/web/interface_projection.py +27 -0
  376. ldcon/web/job_registry.py +68 -0
  377. ldcon/web/job_report.py +122 -0
  378. ldcon/web/laser_operations.py +445 -0
  379. ldcon/web/lock_target_api.py +554 -0
  380. ldcon/web/lock_target_queue.py +212 -0
  381. ldcon/web/log_archive.py +191 -0
  382. ldcon/web/metric_query_profiles.py +378 -0
  383. ldcon/web/online_position.py +26 -0
  384. ldcon/web/operating_mode_api.py +72 -0
  385. ldcon/web/operation_api.py +34 -0
  386. ldcon/web/operation_observation.py +57 -0
  387. ldcon/web/operations_settings.py +92 -0
  388. ldcon/web/quality_threshold_metrics.py +64 -0
  389. ldcon/web/shot_analysis_api.py +136 -0
  390. ldcon/web/sweep_api.py +1186 -0
  391. ldcon/web/sweep_guard.py +45 -0
  392. ldcon/web/target_reference_panel.py +38 -0
  393. ldcon/web/teams_destinations_api.py +81 -0
  394. ldcon/web/web_fleet.py +482 -0
  395. ldcon/web/web_runtime.py +811 -0
  396. ldcon/web/web_server.py +402 -0
  397. ldcon/web/window_api.py +28 -0
  398. ldcon/web_assets/agent-settings.css +4 -0
  399. ldcon/web_assets/agent-settings.html +53 -0
  400. ldcon/web_assets/agent-settings.js +270 -0
  401. ldcon/web_assets/app.css +9 -0
  402. ldcon/web_assets/app.js +802 -0
  403. ldcon/web_assets/card-browser.js +160 -0
  404. ldcon/web_assets/card-guide.js +9 -0
  405. ldcon/web_assets/cards/acquire.metrics.html +82 -0
  406. ldcon/web_assets/cards/agent.history_review.html +13 -0
  407. ldcon/web_assets/cards/agent.log_watch.html +1 -0
  408. ldcon/web_assets/cards/agent.monitor.html +13 -0
  409. ldcon/web_assets/cards/agent.request.html +1 -0
  410. ldcon/web_assets/cards/agent.resident.html +1 -0
  411. ldcon/web_assets/cards/agent.task.html +17 -0
  412. ldcon/web_assets/cards/commission.ecdl_pi.html +1 -0
  413. ldcon/web_assets/cards/identify.axis_frequency.html +1 -0
  414. ldcon/web_assets/cards/identify.current_response.html +1 -0
  415. ldcon/web_assets/cards/laser.emission.html +8 -0
  416. ldcon/web_assets/cards/measure.batch.html +1 -0
  417. ldcon/web_assets/cards/measure.change.html +1 -0
  418. ldcon/web_assets/cards/measure.target_response.html +1 -0
  419. ldcon/web_assets/cards/monitor.wavelength.html +1 -0
  420. ldcon/web_assets/cards/notify.teams.html +1 -0
  421. ldcon/web_assets/cards/optimize.settling_criteria.html +1 -0
  422. ldcon/web_assets/cards/optimize.target_feedforward.html +1 -0
  423. ldcon/web_assets/cards/optimize.target_step.html +1 -0
  424. ldcon/web_assets/cards/optimize.target_thermal.html +1 -0
  425. ldcon/web_assets/cards/plan.ecdl_recovery.html +14 -0
  426. ldcon/web_assets/cards/prepare.ecdl_recovery.html +18 -0
  427. ldcon/web_assets/cards/recover.ecdl_mode_hop.html +12 -0
  428. ldcon/web_assets/cards/report.grafana_dashboard.html +22 -0
  429. ldcon/web_assets/cards/servo.current_pid.html +1 -0
  430. ldcon/web_assets/cards/servo.dlcpro_frequency.html +1 -0
  431. ldcon/web_assets/cards/servo.ecdl_steady_lock.html +11 -0
  432. ldcon/web_assets/cards/servo.native_current.html +1 -0
  433. ldcon/web_assets/cards/study.ecdl_current_response.html +1 -0
  434. ldcon/web_assets/cards/study.ecdl_locked_current_map.html +1 -0
  435. ldcon/web_assets/cards/study.ecdl_mode_map.html +6 -0
  436. ldcon/web_assets/cards/study.ecdl_thermal_context.html +5 -0
  437. ldcon/web_assets/cards/study.target_response.html +1 -0
  438. ldcon/web_assets/cards/sweep.current_modulation.html +1 -0
  439. ldcon/web_assets/cards/sweep.dlcpro_frequency.html +1 -0
  440. ldcon/web_assets/cards/sweep.servo_modulation.html +1 -0
  441. ldcon/web_assets/cards/test.ecdl_recovery.html +4 -0
  442. ldcon/web_assets/cards/tune.ecdl_initial_point.html +1 -0
  443. ldcon/web_assets/cards/tune.ecdl_operating_point.html +13 -0
  444. ldcon/web_assets/cards/tune.ecdl_window_center.html +7 -0
  445. ldcon/web_assets/cards/tune.native_current.html +1 -0
  446. ldcon/web_assets/cards/tune.pid_coordinate_search.html +1 -0
  447. ldcon/web_assets/cards/tune.pid_factor_reduction.html +1 -0
  448. ldcon/web_assets/cards/tune.pid_online.html +1 -0
  449. ldcon/web_assets/cards/tune.temperature_once.html +18 -0
  450. ldcon/web_assets/committing-input.js +35 -0
  451. ldcon/web_assets/configuration-editor.js +194 -0
  452. ldcon/web_assets/dashboard-state.js +39 -0
  453. ldcon/web_assets/failures.html +1 -0
  454. ldcon/web_assets/failures.js +19 -0
  455. ldcon/web_assets/fallback.css +3 -0
  456. ldcon/web_assets/fallback.html +1 -0
  457. ldcon/web_assets/fallback.js +21 -0
  458. ldcon/web_assets/fleet-settings.js +20 -0
  459. ldcon/web_assets/fleet.css +9 -0
  460. ldcon/web_assets/fleet.html +8 -0
  461. ldcon/web_assets/fleet.js +130 -0
  462. ldcon/web_assets/gateway-presentation.js +27 -0
  463. ldcon/web_assets/grafana-help.js +31 -0
  464. ldcon/web_assets/help.html +22 -0
  465. ldcon/web_assets/i18n.js +279 -0
  466. ldcon/web_assets/index.html +14 -0
  467. ldcon/web_assets/laser-workspace.js +279 -0
  468. ldcon/web_assets/modulation.html +1 -0
  469. ldcon/web_assets/modulation.js +14 -0
  470. ldcon/web_assets/reservations.html +16 -0
  471. ldcon/web_assets/reservations.js +77 -0
  472. ldcon/web_assets/teams-settings.js +91 -0
  473. ldcon/web_assets/window-actions.js +37 -0
  474. ldcon/web_assets/workspace.css +99 -0
  475. ldcon/web_fleet.py +4 -0
  476. ldcon/web_server.py +4 -0
  477. ldcon-0.2.0.dist-info/METADATA +85 -0
  478. ldcon-0.2.0.dist-info/RECORD +482 -0
  479. ldcon-0.2.0.dist-info/WHEEL +5 -0
  480. ldcon-0.2.0.dist-info/entry_points.txt +5 -0
  481. ldcon-0.2.0.dist-info/licenses/LICENSE +21 -0
  482. ldcon-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,381 @@
1
+ """Private, bounded event spool for an interactive Claude resident.
2
+
3
+ ``initialize(root, session, cwd)`` creates one immutable connection identity and
4
+ returns its epoch. Never reuse a root for another connection. ``read_events``
5
+ atomically consumes the pending events in durable sequence order. The CLI reads
6
+ one hook JSON object from stdin and emits no hook decision or model context.
7
+ Only ``Stop.last_assistant_message`` is retained as text; prompts are hashed.
8
+ This module does not decide whether a turn completed or approve any operation.
9
+ """
10
+
11
+ from contextlib import contextmanager
12
+ import fcntl
13
+ import hashlib
14
+ import json
15
+ import os
16
+ from pathlib import Path
17
+ import re
18
+ import stat
19
+ import sys
20
+ import time
21
+ import uuid
22
+
23
+ from ldcon.agents.failure_policy import URL
24
+
25
+ EVENTS = frozenset({'SessionStart', 'UserPromptSubmit', 'Stop', 'StopFailure',
26
+ 'SessionEnd', 'PostModelSwitch', 'ModelStatus'})
27
+ MAX_INPUT_BYTES = 1024 * 1024
28
+ MAX_PROMPT_BYTES = 262144
29
+ MAX_MESSAGE_BYTES = 32768
30
+ MAX_EVENTS = 64
31
+ MAX_STATE_BYTES = 8 * 1024 * 1024
32
+ LOCK_TIMEOUT_S = 5.
33
+ DIRECTORY_FLAGS = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC
34
+ FILE_FLAGS = os.O_NOFOLLOW | os.O_NONBLOCK | os.O_CLOEXEC
35
+ IDENTIFIER = re.compile(r'[A-Za-z0-9][A-Za-z0-9_.-]{0,127}')
36
+
37
+
38
+ class ProtocolError(RuntimeError):
39
+ """The spool cannot safely represent or consume this connection."""
40
+
41
+
42
+ def _uuid(value):
43
+ try:
44
+ return isinstance(value, str) and str(uuid.UUID(value)) == value
45
+ except (ValueError, AttributeError):
46
+ return False
47
+
48
+
49
+ def _directory(root, *, private=True):
50
+ """Reject symlinks in every path component, including the private root."""
51
+ path = Path(os.path.abspath(root))
52
+ descriptor = os.open(path.anchor, DIRECTORY_FLAGS)
53
+ try:
54
+ for component in path.parts[1:]:
55
+ child = os.open(component, DIRECTORY_FLAGS, dir_fd=descriptor)
56
+ os.close(descriptor)
57
+ descriptor = child
58
+ info = os.fstat(descriptor)
59
+ if private and (info.st_uid != os.getuid() or stat.S_IMODE(info.st_mode) & 0o077):
60
+ raise ProtocolError('Hook spool directory must be private and owned by this user')
61
+ return descriptor
62
+ except BaseException:
63
+ os.close(descriptor)
64
+ raise
65
+
66
+
67
+ def _check_file(descriptor, limit):
68
+ info = os.fstat(descriptor)
69
+ if (not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or
70
+ info.st_uid != os.getuid() or stat.S_IMODE(info.st_mode) & 0o077):
71
+ raise ProtocolError('Hook spool file is not a private single-link regular file')
72
+ if info.st_size > limit:
73
+ raise ProtocolError('Hook spool file exceeds its size limit')
74
+
75
+
76
+ @contextmanager
77
+ def _locked(root):
78
+ directory = _directory(root)
79
+ lock = None
80
+ try:
81
+ lock = os.open('lock', os.O_RDWR | os.O_CREAT | FILE_FLAGS, 0o600, dir_fd=directory)
82
+ _check_file(lock, 0)
83
+ deadline = time.monotonic() + LOCK_TIMEOUT_S
84
+ while True:
85
+ try:
86
+ fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
87
+ break
88
+ except BlockingIOError:
89
+ if time.monotonic() >= deadline:
90
+ raise ProtocolError('Hook spool lock deadline exceeded')
91
+ time.sleep(.01)
92
+ yield directory
93
+ finally:
94
+ if lock is not None:
95
+ os.close(lock)
96
+ os.close(directory)
97
+
98
+
99
+ def _load(directory):
100
+ descriptor = os.open('state.json', os.O_RDONLY | FILE_FLAGS, dir_fd=directory)
101
+ try:
102
+ _check_file(descriptor, MAX_STATE_BYTES)
103
+ with os.fdopen(descriptor, 'rb', closefd=False) as stream:
104
+ raw = stream.read(MAX_STATE_BYTES + 1)
105
+ if len(raw) > MAX_STATE_BYTES:
106
+ raise ProtocolError('Hook spool state exceeds its size limit')
107
+ state = json.loads(raw)
108
+ except (ValueError, UnicodeError) as error:
109
+ raise ProtocolError('Hook spool state is invalid') from error
110
+ finally:
111
+ os.close(descriptor)
112
+ if (not isinstance(state, dict) or state.get('schema_version') != 1 or
113
+ not _uuid(state.get('epoch')) or not _uuid(state.get('session_id')) or
114
+ not isinstance(state.get('cwd'), str) or not os.path.isabs(state['cwd']) or
115
+ type(state.get('next_sequence')) is not int or state['next_sequence'] < 1 or
116
+ type(state.get('faulted')) is not bool or
117
+ not isinstance(state.get('events'), list) or len(state['events']) > MAX_EVENTS):
118
+ raise ProtocolError('Hook spool state has an invalid shape')
119
+ previous = 0
120
+ for event in state['events']:
121
+ if (not isinstance(event, dict) or event.get('epoch') != state['epoch'] or
122
+ type(event.get('sequence')) is not int or
123
+ not previous < event['sequence'] < state['next_sequence']):
124
+ raise ProtocolError('Hook spool sequence or epoch is invalid')
125
+ previous = event['sequence']
126
+ return state
127
+
128
+
129
+ def _save(directory, state):
130
+ data = (json.dumps(state, ensure_ascii=False, separators=(',', ':')) + '\n').encode('utf-8')
131
+ if len(data) > MAX_STATE_BYTES:
132
+ raise ProtocolError('Hook spool state exceeds its size limit')
133
+ temporary = '.state-' + uuid.uuid4().hex
134
+ descriptor = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | FILE_FLAGS,
135
+ 0o600, dir_fd=directory)
136
+ try:
137
+ with os.fdopen(descriptor, 'wb') as stream:
138
+ stream.write(data)
139
+ stream.flush()
140
+ os.fsync(stream.fileno())
141
+ os.replace(temporary, 'state.json', src_dir_fd=directory, dst_dir_fd=directory)
142
+ os.fsync(directory)
143
+ finally:
144
+ try:
145
+ os.unlink(temporary, dir_fd=directory)
146
+ except FileNotFoundError:
147
+ pass
148
+
149
+
150
+ def initialize(root, session, cwd):
151
+ """Create a connection spool; refuse replacing even a fully consumed spool."""
152
+ if not _uuid(session) or not isinstance(cwd, (str, Path)) or not os.path.isabs(cwd):
153
+ raise ValueError('Canonical session UUID and absolute workspace required')
154
+ cwd = os.path.abspath(cwd)
155
+ if not Path(cwd).is_dir():
156
+ raise ValueError('Existing workspace required')
157
+ root = Path(os.path.abspath(root))
158
+ parent = _directory(root.parent, private=False)
159
+ try:
160
+ try:
161
+ os.mkdir(root.name, mode=0o700, dir_fd=parent)
162
+ except FileExistsError:
163
+ pass
164
+ finally:
165
+ os.close(parent)
166
+ with _locked(root) as directory:
167
+ try:
168
+ os.stat('state.json', dir_fd=directory, follow_symlinks=False)
169
+ except FileNotFoundError:
170
+ pass
171
+ else:
172
+ raise ProtocolError('Hook spool already initialized; use a new connection directory')
173
+ state = dict(schema_version=1, epoch=str(uuid.uuid4()), session_id=session,
174
+ cwd=cwd, next_sequence=1, faulted=False, events=[])
175
+ _save(directory, state)
176
+ return state['epoch']
177
+
178
+
179
+ def _safe_message(message):
180
+ """Bound retained prose and redact transport URLs/credential-like strings."""
181
+ raw = message.encode('utf-8')
182
+ bounded = raw[:MAX_MESSAGE_BYTES].decode('utf-8', errors='ignore')
183
+ bounded = re.sub(r'\x1b\[[0-?]*[ -/]*[@-~]', '', bounded)
184
+ bounded = ''.join(c for c in bounded if c in '\n\t' or (ord(c) >= 32 and ord(c) != 127))
185
+ bounded = URL.sub('[URL redacted]', bounded)
186
+ bounded = re.sub(r'\b(?:Bearer|Basic)\s+[A-Za-z0-9+/=._-]+', '[authorization redacted]',
187
+ bounded, flags=re.I)
188
+ bounded = re.sub(r'\b(?:password|secret|token|api_key|authorization)\s*[:=]\s*'
189
+ r'(?:"[^"]*"|\x27[^\x27]*\x27|[^\s,;]+)', '[credential redacted]',
190
+ bounded, flags=re.I)
191
+ encoded = bounded.encode('utf-8')
192
+ return (encoded[:MAX_MESSAGE_BYTES].decode('utf-8', errors='ignore'),
193
+ len(raw) > MAX_MESSAGE_BYTES or len(encoded) > MAX_MESSAGE_BYTES)
194
+
195
+
196
+ def _normalize(state, payload):
197
+ if not isinstance(payload, dict):
198
+ return None, 'invalid_input_shape'
199
+ kind = payload.get('hook_event_name')
200
+ if not isinstance(kind, str) or kind not in EVENTS:
201
+ return None, 'unsupported_event'
202
+ if payload.get('session_id') != state['session_id']:
203
+ return None, 'session_mismatch'
204
+ cwd = payload.get('cwd')
205
+ if not isinstance(cwd, str) or not os.path.isabs(cwd):
206
+ return None, 'cwd_mismatch'
207
+ try:
208
+ if not Path(cwd).resolve(strict=True).is_relative_to(Path(state['cwd']).resolve(strict=True)):
209
+ return None, 'cwd_mismatch'
210
+ except (OSError, ValueError, RuntimeError):
211
+ return None, 'cwd_mismatch'
212
+ if 'epoch' in payload and payload['epoch'] != state['epoch']:
213
+ return None, 'epoch_mismatch'
214
+ event = dict(event=kind, session_id=state['session_id'], cwd=state['cwd'])
215
+ prompt = payload.get('prompt_id')
216
+ if kind in ('UserPromptSubmit', 'Stop', 'StopFailure') and not _uuid(prompt):
217
+ return None, 'missing_or_invalid_prompt_id'
218
+ if prompt is not None:
219
+ if not _uuid(prompt):
220
+ return None, 'invalid_prompt_id'
221
+ event['prompt_id'] = prompt
222
+ if payload.get('agent_id'):
223
+ return None, 'unexpected_subagent_event'
224
+ if kind == 'UserPromptSubmit':
225
+ text = payload.get('prompt')
226
+ if not isinstance(text, str):
227
+ return None, 'invalid_prompt'
228
+ encoded = text.encode('utf-8')
229
+ if len(encoded) > MAX_PROMPT_BYTES:
230
+ return None, 'prompt_too_large'
231
+ event['prompt_sha256'] = hashlib.sha256(encoded).hexdigest()
232
+ # The interactive editor can normalize JSON whitespace. Retain a second
233
+ # digest only for a complete JSON object; never retain or partially
234
+ # extract its content. Duplicate keys/nonfinite values are ambiguous.
235
+ def unique_object(pairs):
236
+ result = {}
237
+ for key, value in pairs:
238
+ if key in result:
239
+ raise ValueError('Duplicate JSON key')
240
+ result[key] = value
241
+ return result
242
+ def reject_constant(value):
243
+ raise ValueError('Nonfinite JSON value')
244
+ try:
245
+ structured = json.loads(text, object_pairs_hook=unique_object,
246
+ parse_constant=reject_constant)
247
+ if isinstance(structured, dict):
248
+ canonical = json.dumps(structured, ensure_ascii=True, separators=(',', ':'),
249
+ sort_keys=True, allow_nan=False).encode('utf-8')
250
+ if len(canonical) <= MAX_PROMPT_BYTES:
251
+ event['prompt_json_sha256'] = hashlib.sha256(canonical).hexdigest()
252
+ except (ValueError, TypeError, RecursionError):
253
+ pass
254
+ if kind == 'Stop':
255
+ message = payload.get('last_assistant_message')
256
+ if not isinstance(message, str):
257
+ return None, 'missing_or_invalid_assistant_message'
258
+ event['last_assistant_message'], event['message_truncated'] = _safe_message(message)
259
+ event['stop_hook_active'] = payload.get('stop_hook_active') is True
260
+ event['background_work_present'] = bool(payload.get('background_tasks') or payload.get('session_crons'))
261
+ # Error details may contain credentials; retain only an identifier, never
262
+ # StopFailure's rendered API error in last_assistant_message.
263
+ for key in {'SessionStart': ('source', 'model'), 'ModelStatus': ('model',), 'StopFailure': ('error',),
264
+ 'SessionEnd': ('reason',), 'PostModelSwitch': ('from_model', 'to_model')}.get(kind, ()):
265
+ value = payload.get(key)
266
+ if value is not None:
267
+ if not isinstance(value, str) or not IDENTIFIER.fullmatch(value):
268
+ return None, 'invalid_' + key
269
+ event[key] = value
270
+ mode = payload.get('permission_mode')
271
+ if mode is not None:
272
+ if mode not in ('default', 'plan', 'acceptEdits', 'auto', 'dontAsk', 'bypassPermissions'):
273
+ return None, 'invalid_permission_mode'
274
+ event['permission_mode'] = mode
275
+ effort = payload.get('effort')
276
+ if isinstance(effort, dict) and effort.get('level') in ('low', 'medium', 'high', 'xhigh', 'max'):
277
+ event['effort'] = dict(level=effort['level'])
278
+ return event, None
279
+
280
+
281
+ def record_model_status(root, payload):
282
+ """Consume official statusLine metadata, not terminal text or model prose.
283
+
284
+ Keep only this session's model/effort. Repeated identical UI refreshes are
285
+ coalesced; distinct transitions remain ordered with the completion hooks.
286
+ Nothing here authorizes a tool or acknowledges a submitted request.
287
+ """
288
+ if not isinstance(payload, dict):
289
+ return record_event(root, None, input_error='invalid_model_status')
290
+ model = payload.get('model')
291
+ model = model.get('id') if isinstance(model, dict) else None
292
+ if not isinstance(model, str) or not IDENTIFIER.fullmatch(model):
293
+ return record_event(root, None, input_error='invalid_status_model')
294
+ return record_event(root, dict(hook_event_name='ModelStatus',
295
+ session_id=payload.get('session_id'), cwd=payload.get('cwd'), model=model,
296
+ effort=payload.get('effort')))
297
+
298
+
299
+ def record_event(root, payload, *, input_error=None):
300
+ """Durably record one allowlisted event, or a latched protocol error.
301
+
302
+ The returned record contains no submitted prompt. Once faulted, later hooks
303
+ cannot manufacture completion records. Overflow discards pending records in
304
+ favor of one explicit error, so a queued Stop cannot hide the overflow.
305
+ """
306
+ with _locked(root) as directory:
307
+ state = _load(directory)
308
+ if state['faulted']:
309
+ return None
310
+ try:
311
+ event, reason = _normalize(state, payload) if input_error is None else (None, input_error)
312
+ except (UnicodeError, TypeError, ValueError):
313
+ event, reason = None, 'invalid_input_encoding'
314
+ if not reason and event['event'] == 'ModelStatus':
315
+ previous = state.get('last_model_status', {})
316
+ same = all(previous.get(k) == event.get(k) for k in ('model', 'effort'))
317
+ state['last_model_status'] = {**event, 'observed_unix_s': time.time()}
318
+ if same:
319
+ _save(directory, state)
320
+ return event
321
+ if len(state['events']) >= MAX_EVENTS:
322
+ reason = 'spool_overflow'
323
+ state['events'] = []
324
+ if reason:
325
+ kind = payload.get('hook_event_name') if isinstance(payload, dict) else None
326
+ event = dict(event='protocol_error', reason=reason,
327
+ source_event=kind if isinstance(kind, str) and kind in EVENTS else None,
328
+ session_id=state['session_id'], cwd=state['cwd'])
329
+ state['faulted'] = True
330
+ state['last_protocol_error'] = dict(reason=reason, source_event=event['source_event'])
331
+ event.update(epoch=state['epoch'], sequence=state['next_sequence'])
332
+ state['next_sequence'] += 1
333
+ state['events'].append(event)
334
+ _save(directory, state)
335
+ return event
336
+
337
+
338
+ def read_events(root):
339
+ """Consume queued records exactly once under the same writer lock.
340
+
341
+ Callers must inspect the whole batch for protocol_error before accepting a
342
+ completion. Failure after this consumption is an unknown turn, not replay.
343
+ """
344
+ with _locked(root) as directory:
345
+ state = _load(directory)
346
+ events = state['events']
347
+ if events:
348
+ state['events'] = []
349
+ _save(directory, state)
350
+ return events
351
+
352
+
353
+ def main(argv=None):
354
+ args = sys.argv[1:] if argv is None else argv
355
+ model_status = len(args) == 2 and args[1] == '--model-status'
356
+ if len(args) != 1 and not model_status:
357
+ print('Interactive hook requires one private spool directory', file=sys.stderr)
358
+ return 1
359
+ try:
360
+ raw = sys.stdin.buffer.read(MAX_INPUT_BYTES + 1)
361
+ if len(raw) > MAX_INPUT_BYTES:
362
+ record_event(args[0], None, input_error='input_too_large')
363
+ else:
364
+ try:
365
+ payload = json.loads(raw)
366
+ except (ValueError, UnicodeError, RecursionError):
367
+ record_event(args[0], None, input_error='invalid_input_json')
368
+ else:
369
+ if model_status:
370
+ record_model_status(args[0], payload)
371
+ else:
372
+ record_event(args[0], payload)
373
+ return 0
374
+ except (OSError, ValueError, ProtocolError):
375
+ # Never expose arbitrary input, filesystem paths, or authentication text.
376
+ print('Interactive hook spool unavailable', file=sys.stderr)
377
+ return 1
378
+
379
+
380
+ if __name__ == '__main__':
381
+ raise SystemExit(main())
@@ -0,0 +1,48 @@
1
+ """Language policy shared by resident instructions and automated reviews."""
2
+
3
+ LANGUAGE_INSTRUCTIONS = (
4
+ 'Use English for system instructions, internal operational responses, startup and '
5
+ 'reconnection notices, automated reviews, and unsolicited system alerts. '
6
+ 'When answering a human request or asking a follow-up about it, use the language '
7
+ 'of the original human request, including requests relayed through Teams or '
8
+ 'agentbridge. An explicit language preference in that request takes precedence. '
9
+ 'Do not infer the human language from an English system wrapper, translated '
10
+ 'instructions, logs, or the connector. An automated log review or card failure '
11
+ 'without an originating human request is system work and uses English, even if '
12
+ 'its configuration or evidence contains another language. Preserve the registered '
13
+ '[from: <your-registered-id>] Teams prefix in every language.'
14
+ )
15
+
16
+ TIME_INSTRUCTIONS = (
17
+ 'Use the execution machine\'s current local timezone for human-readable timestamps '
18
+ 'in replies, reports, Teams messages and automated reviews. Determine it from the '
19
+ 'execution machine OS, not the browser, user device or an assumed UTC timezone. '
20
+ 'Include the calendar date and timezone abbreviation or numeric UTC offset; apply '
21
+ 'the offset appropriate to each event date, including daylight-saving transitions. '
22
+ 'Convert UTC or epoch evidence for presentation while preserving the original '
23
+ 'timestamps and storage format; never change measurement clocks, scheduling, '
24
+ 'retention or machine timezone to implement this display policy. Label source UTC '
25
+ 'explicitly if quoting it. Resolve ambiguous local times with the numeric offset. '
26
+ 'If the machine timezone cannot be verified, say so rather than inventing it.'
27
+ )
28
+ LANGUAGE_INSTRUCTIONS += '\n' + TIME_INSTRUCTIONS
29
+
30
+
31
+ NOTIFICATION_INSTRUCTIONS = (
32
+ 'Automatic Teams notices use short English summaries, normally three to five lines: '
33
+ 'registered sender, event/outcome, dated lock and optical-quality state, and next action '
34
+ 'or decision. Supply notice_json structured facts to teams_notify (event, event_id, '
35
+ 'case_id or operation_id, summary, action, observed_at with offset, lock_state, '
36
+ 'optical_quality and optional facts). Keep full evidence locally. Do not send raw JSON, '
37
+ 'tracebacks, reasoning transcripts, long paths or invented attachments. The host applies '
38
+ 'the current notification policy and trusted human-request origin; never choose reporting '
39
+ 'granularity or pretend an automated review originated from a human. Healthy unchanged '
40
+ 'checks are local only; intermediate progress may be grouped. Do not bypass grouping '
41
+ 'with new request IDs. Human replies keep their original request language and correlation. '
42
+ 'Missing dated state evidence means unverified, never locked. Optical degradation does '
43
+ 'not imply frequency-lock loss. Supply exact questions and complete changes for approvals; '
44
+ 'never truncate values, units, scope, uncertainty or the required decision. A review alert '
45
+ 'alone grants no approval. Queued, HTTP-accepted and human-confirmed delivery are distinct. '
46
+ 'Never replay unknown sends or old pending notices because policy/formatting changed.'
47
+ )
48
+ LANGUAGE_INSTRUCTIONS += '\n' + NOTIFICATION_INSTRUCTIONS