writer 0.8.3rc21__py3-none-any.whl → 1.25.1rc1__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 (265) hide show
  1. writer/ai/__init__.py +277 -28
  2. writer/app_runner.py +236 -32
  3. writer/app_templates/default/.wf/components-blueprints_blueprint-0-0decp3w5erhvl0nw.jsonl +11 -0
  4. writer/app_templates/default/.wf/components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl +22 -10
  5. writer/app_templates/default/.wf/components-root.jsonl +1 -1
  6. writer/app_templates/default/.wf/components-workflows_root.jsonl +1 -0
  7. writer/app_templates/default/.wf/components-workflows_workflow-0-lfltcky7l1fsm6j2.jsonl +1 -0
  8. writer/app_templates/default/.wf/metadata.json +1 -1
  9. writer/app_templates/default/README.md +3 -0
  10. writer/app_templates/default/main.py +8 -5
  11. writer/app_templates/default/requirements.txt +1 -0
  12. writer/app_templates/default/static/agent_builder_demo.png +0 -0
  13. writer/app_templates/hello/main.py +3 -0
  14. writer/auth.py +7 -2
  15. writer/autogen.py +26 -9
  16. writer/blocks/__init__.py +21 -1
  17. writer/blocks/addtostatelist.py +5 -4
  18. writer/blocks/apitrigger.py +45 -0
  19. writer/blocks/base_block.py +134 -14
  20. writer/blocks/changepage.py +1 -1
  21. writer/blocks/code.py +27 -11
  22. writer/blocks/crontrigger.py +49 -0
  23. writer/blocks/foreach.py +3 -3
  24. writer/blocks/httprequest.py +8 -24
  25. writer/blocks/ifelse.py +71 -0
  26. writer/blocks/logmessage.py +2 -2
  27. writer/blocks/returnvalue.py +2 -2
  28. writer/blocks/runblueprint.py +2 -2
  29. writer/blocks/setstate.py +5 -5
  30. writer/blocks/sharedblueprint.py +86 -0
  31. writer/blocks/writeraddchatmessage.py +45 -7
  32. writer/blocks/writeraddtokg.py +31 -4
  33. writer/blocks/writeraskkg.py +27 -34
  34. writer/blocks/writerchat.py +14 -9
  35. writer/blocks/writerchatreply.py +279 -0
  36. writer/blocks/writerchatreplywithtoolconfig.py +393 -0
  37. writer/blocks/writerclassification.py +42 -5
  38. writer/blocks/writercompletion.py +4 -4
  39. writer/blocks/writerfileapi.py +4 -4
  40. writer/blocks/writerinitchat.py +5 -4
  41. writer/blocks/writerkeyvaluestorage.py +106 -0
  42. writer/blocks/writernocodeapp.py +4 -4
  43. writer/blocks/writerparsepdf.py +8 -6
  44. writer/blocks/writerstructuredoutput.py +105 -0
  45. writer/blocks/writertoolcalling.py +106 -51
  46. writer/blocks/writervision.py +141 -0
  47. writer/blocks/writerwebsearch.py +175 -0
  48. writer/blueprints.py +715 -251
  49. writer/command_line.py +52 -16
  50. writer/core.py +200 -35
  51. writer/core_ui.py +4 -0
  52. writer/evaluator.py +38 -24
  53. writer/journal.py +227 -0
  54. writer/keyvalue_storage.py +93 -0
  55. writer/logs.py +277 -0
  56. writer/serve.py +402 -198
  57. writer/ss_types.py +41 -0
  58. writer/static/assets/BaseMarkdown-Wrvby5J8.js +1 -0
  59. writer/static/assets/BlueprintToolbar-BuXNRxWT.js +1 -0
  60. writer/static/assets/BlueprintToolbar-wpfX0jo_.css +1 -0
  61. writer/static/assets/BuilderApp-PTOI76jZ.js +8 -0
  62. writer/static/assets/BuilderApp-WimUfNZr.css +1 -0
  63. writer/static/assets/BuilderApplicationSelect-DXzy4e_h.js +7 -0
  64. writer/static/assets/BuilderApplicationSelect-XaM1D5fv.css +1 -0
  65. writer/static/assets/BuilderBlueprintLibraryPanel-Ckrhknlh.css +1 -0
  66. writer/static/assets/BuilderBlueprintLibraryPanel-DBDzhTlc.js +1 -0
  67. writer/static/assets/{BuilderEmbeddedCodeEditor-DiDqfWdt.css → BuilderEmbeddedCodeEditor-B0bcjlhk.css} +1 -1
  68. writer/static/assets/{BuilderEmbeddedCodeEditor-CbK-r9w6.js → BuilderEmbeddedCodeEditor-Dn7eDICN.js} +7 -7
  69. writer/static/assets/BuilderGraphSelect-C-LRsO8W.js +7 -0
  70. writer/static/assets/BuilderGraphSelect-D7B61d5s.css +1 -0
  71. writer/static/assets/{BuilderInsertionLabel-CDlWX-mE.js → BuilderInsertionLabel-BhyL9wgn.js} +1 -1
  72. writer/static/assets/{BuilderInsertionOverlay-B7-TsHNC.js → BuilderInsertionOverlay-MkAIVruY.js} +1 -1
  73. writer/static/assets/BuilderJournal-A0LcEwGI.js +7 -0
  74. writer/static/assets/BuilderJournal-DHv3Pvvm.css +1 -0
  75. writer/static/assets/BuilderModelSelect-CdSo_sih.js +7 -0
  76. writer/static/assets/BuilderModelSelect-Dc4IPLp2.css +1 -0
  77. writer/static/assets/BuilderSettings-BDwZBveu.js +16 -0
  78. writer/static/assets/BuilderSettings-lZkOXEYw.css +1 -0
  79. writer/static/assets/BuilderSettingsArtifactAPITriggerDetails-3O6jKBXD.js +4 -0
  80. writer/static/assets/BuilderSettingsArtifactAPITriggerDetails-DnX66iRg.css +1 -0
  81. writer/static/assets/BuilderSettingsDeploySharedBlueprint-BR_3ptsd.js +1 -0
  82. writer/static/assets/BuilderSettingsDeploySharedBlueprint-KJTl8gxP.css +1 -0
  83. writer/static/assets/BuilderSettingsHandlers-CBtEQFSo.js +1 -0
  84. writer/static/assets/BuilderSettingsHandlers-DJPeASfz.css +1 -0
  85. writer/static/assets/BuilderSidebarComponentTree-DLltgas5.js +1 -0
  86. writer/static/assets/BuilderSidebarComponentTree-DYu1F793.css +1 -0
  87. writer/static/assets/BuilderSidebarToolkit-CApZNTAq.js +7 -0
  88. writer/static/assets/BuilderSidebarToolkit-CwqbjRv8.css +1 -0
  89. writer/static/assets/BuilderTemplateEditor-CYSDeWgV.css +1 -0
  90. writer/static/assets/BuilderTemplateEditor-DnRDRcA0.js +87 -0
  91. writer/static/assets/BuilderVault-2vGoV0sx.js +1 -0
  92. writer/static/assets/BuilderVault-Cx6oQSES.css +1 -0
  93. writer/static/assets/ComponentRenderer-72hqvEvI.css +1 -0
  94. writer/static/assets/ComponentRenderer-D4Pj1i3s.js +1 -0
  95. writer/static/assets/SharedCopyClipboardButton-BipJKGtz.css +1 -0
  96. writer/static/assets/SharedCopyClipboardButton-DNI9kLe6.js +1 -0
  97. writer/static/assets/WdsCheckbox-DKvpPA4D.css +1 -0
  98. writer/static/assets/WdsCheckbox-edQcn1cf.js +1 -0
  99. writer/static/assets/WdsDropdownMenu-CzzPN9Wg.css +1 -0
  100. writer/static/assets/WdsDropdownMenu-DQnrRBNV.js +1 -0
  101. writer/static/assets/WdsFieldWrapper-Cmufx5Nj.js +1 -0
  102. writer/static/assets/WdsFieldWrapper-CsemOh8D.css +1 -0
  103. writer/static/assets/WdsTabs-DKj7BqI0.css +1 -0
  104. writer/static/assets/WdsTabs-DcfY_zn5.js +1 -0
  105. writer/static/assets/{art-paper-WsD9P5Lu.svg → art-paper-D70v1WMA.svg} +0 -1
  106. writer/static/assets/{cssMode-6B7VrieQ.js → cssMode-BYq4oZGq.js} +1 -1
  107. writer/static/assets/{freemarker2-Dy54TNCQ.js → freemarker2-CnNourkO.js} +1 -1
  108. writer/static/assets/{handlebars-BnWqX2x5.js → handlebars-Bm22yapJ.js} +1 -1
  109. writer/static/assets/{html-CIuj_eOg.js → html-CAKAfoZF.js} +1 -1
  110. writer/static/assets/{htmlMode-5fUQN2xJ.js → htmlMode-BGZ97n-V.js} +1 -1
  111. writer/static/assets/index-BKNuk68o.css +1 -0
  112. writer/static/assets/index-BQNXU3IR.js +17 -0
  113. writer/static/assets/index-DHXAd5Yn.js +4 -0
  114. writer/static/assets/index-Zki-pfO-.js +8525 -0
  115. writer/static/assets/index.esm-B1ZQtduY.js +17 -0
  116. writer/static/assets/{javascript-PLzaI1wY.js → javascript-X1f02eyK.js} +1 -1
  117. writer/static/assets/{jsonMode-CzZfZ4-D.js → jsonMode-hT0bNgT8.js} +1 -1
  118. writer/static/assets/{liquid-Cy21vWLb.js → liquid-KmCCiJw2.js} +1 -1
  119. writer/static/assets/{mapbox-gl-BjXsUCYi.js → mapbox-gl-C0cyFYYW.js} +1 -1
  120. writer/static/assets/{mdx-Cgik3q5p.js → mdx-DtRFauUw.js} +1 -1
  121. writer/static/assets/pdf-B6-yWJ-Y.js +12 -0
  122. writer/static/assets/pdf.worker.min-CyUfim15.mjs +21 -0
  123. writer/static/assets/{plotly.min-Dk-1ahEu.js → plotly.min-DutuuatZ.js} +1 -1
  124. writer/static/assets/{python-h6gjz_bN.js → python-DVhxg746.js} +1 -1
  125. writer/static/assets/{razor-BQ1k9241.js → razor-DR5Ns_BC.js} +1 -1
  126. writer/static/assets/{tsMode-BaBWt05D.js → tsMode-BNUEZzir.js} +1 -1
  127. writer/static/assets/{typescript-B42-gYGT.js → typescript-CRVt7Hx0.js} +1 -1
  128. writer/static/assets/useBlueprintRun-C00bCxh-.js +1 -0
  129. writer/static/assets/useKeyValueEditor-nDmI7cTJ.js +1 -0
  130. writer/static/assets/useListResources-DLkZhRSJ.js +1 -0
  131. writer/static/assets/{xml-BOez4Prd.js → xml-C_6-t1tb.js} +1 -1
  132. writer/static/assets/{yaml-BQhoEOIz.js → yaml-DIw8G7jk.js} +1 -1
  133. writer/static/components/annotatedtext.svg +3 -3
  134. writer/static/components/avatar.svg +3 -3
  135. writer/static/components/blueprints_addtostatelist.svg +3 -3
  136. writer/static/components/blueprints_apitrigger.svg +4 -0
  137. writer/static/components/blueprints_category_Logic.svg +3 -3
  138. writer/static/components/blueprints_category_Other.svg +3 -3
  139. writer/static/components/blueprints_category_Writer.svg +24 -5
  140. writer/static/components/blueprints_code.svg +6 -6
  141. writer/static/components/blueprints_crontrigger.svg +6 -0
  142. writer/static/components/blueprints_foreach.svg +3 -3
  143. writer/static/components/blueprints_httprequest.svg +6 -6
  144. writer/static/components/blueprints_logmessage.svg +10 -3
  145. writer/static/components/blueprints_parsejson.svg +3 -3
  146. writer/static/components/blueprints_returnvalue.svg +3 -3
  147. writer/static/components/blueprints_runblueprint.svg +3 -3
  148. writer/static/components/blueprints_setstate.svg +3 -3
  149. writer/static/components/blueprints_writeraddchatmessage.svg +14 -6
  150. writer/static/components/blueprints_writeraddtokg.svg +14 -6
  151. writer/static/components/blueprints_writerchatreply.svg +19 -0
  152. writer/static/components/blueprints_writerclassification.svg +23 -8
  153. writer/static/components/blueprints_writercompletion.svg +13 -5
  154. writer/static/components/blueprints_writernocodeapp.svg +13 -3
  155. writer/static/components/button.svg +3 -3
  156. writer/static/components/category_Content.svg +3 -3
  157. writer/static/components/category_Embed.svg +3 -3
  158. writer/static/components/category_Input.svg +4 -4
  159. writer/static/components/category_Layout.svg +6 -6
  160. writer/static/components/category_Other.svg +3 -3
  161. writer/static/components/chatbot.svg +3 -3
  162. writer/static/components/checkboxinput.svg +3 -3
  163. writer/static/components/colorinput.svg +6 -6
  164. writer/static/components/column.svg +3 -3
  165. writer/static/components/columns.svg +3 -3
  166. writer/static/components/dataframe.svg +3 -3
  167. writer/static/components/dateinput.svg +3 -3
  168. writer/static/components/dropdowninput.svg +4 -4
  169. writer/static/components/fileinput.svg +3 -3
  170. writer/static/components/googlemaps.svg +3 -3
  171. writer/static/components/heading.svg +6 -6
  172. writer/static/components/horizontalstack.svg +3 -3
  173. writer/static/components/html.svg +6 -6
  174. writer/static/components/icon.svg +3 -3
  175. writer/static/components/iframe.svg +3 -3
  176. writer/static/components/image.svg +10 -3
  177. writer/static/components/jsonviewer.svg +3 -3
  178. writer/static/components/link.svg +7 -7
  179. writer/static/components/mapbox.svg +3 -3
  180. writer/static/components/message.svg +3 -3
  181. writer/static/components/metric.svg +3 -3
  182. writer/static/components/multiselectinput.svg +3 -3
  183. writer/static/components/numberinput.svg +3 -3
  184. writer/static/components/pagination.svg +3 -3
  185. writer/static/components/pdf.svg +3 -3
  186. writer/static/components/plotlygraph.svg +6 -6
  187. writer/static/components/progressbar.svg +4 -4
  188. writer/static/components/radioinput.svg +3 -3
  189. writer/static/components/rangeinput.svg +3 -3
  190. writer/static/components/ratinginput.svg +3 -3
  191. writer/static/components/repeater.svg +3 -3
  192. writer/static/components/reuse.svg +3 -3
  193. writer/static/components/section.svg +3 -3
  194. writer/static/components/selectinput.svg +4 -4
  195. writer/static/components/separator.svg +3 -3
  196. writer/static/components/sidebar.svg +3 -3
  197. writer/static/components/sliderinput.svg +3 -3
  198. writer/static/components/step.svg +3 -3
  199. writer/static/components/steps.svg +3 -3
  200. writer/static/components/switchinput.svg +3 -3
  201. writer/static/components/tab.svg +3 -3
  202. writer/static/components/tabs.svg +3 -3
  203. writer/static/components/tags.svg +10 -3
  204. writer/static/components/text.svg +3 -3
  205. writer/static/components/textareainput.svg +10 -3
  206. writer/static/components/textinput.svg +3 -3
  207. writer/static/components/timeinput.svg +3 -3
  208. writer/static/components/timer.svg +3 -3
  209. writer/static/components/videoplayer.svg +10 -3
  210. writer/static/components/webcamcapture.svg +3 -3
  211. writer/static/index.html +3 -11
  212. writer/static/status/cancelled.svg +5 -0
  213. writer/static/status/skipped.svg +4 -0
  214. writer/static/status/stopped.svg +4 -0
  215. writer/sync.py +431 -0
  216. writer/ui.py +49 -41
  217. writer/vault.py +48 -0
  218. writer/wf_project.py +5 -5
  219. writer-1.25.1rc1.dist-info/METADATA +92 -0
  220. writer-1.25.1rc1.dist-info/RECORD +382 -0
  221. {writer-0.8.3rc21.dist-info → writer-1.25.1rc1.dist-info}/WHEEL +1 -1
  222. writer/app_templates/default/.wf/components-blueprints_blueprint-0-t84xyhxau9ej3823.jsonl +0 -18
  223. writer/app_templates/default/static/welcome.svg +0 -40
  224. writer/static/assets/BaseMarkdown-BH_nSq9H.js +0 -1
  225. writer/static/assets/BlueprintToolbar-BO-WERxH.css +0 -1
  226. writer/static/assets/BlueprintToolbar-CHWL-rTm.js +0 -1
  227. writer/static/assets/BuilderApp-0XXiQ2l7.js +0 -7
  228. writer/static/assets/BuilderApp-CAhvLO4a.css +0 -1
  229. writer/static/assets/BuilderApplicationSelect-CwzU4F1-.js +0 -7
  230. writer/static/assets/BuilderApplicationSelect-DYYFtqjx.css +0 -1
  231. writer/static/assets/BuilderGraphSelect-CVO4gzts.css +0 -1
  232. writer/static/assets/BuilderGraphSelect-DV5Xy0HK.js +0 -7
  233. writer/static/assets/BuilderInstanceTracker-BECcXNnW.css +0 -1
  234. writer/static/assets/BuilderInstanceTracker-Dr0XhpSH.js +0 -1
  235. writer/static/assets/BuilderModelSelect-QHUGd86u.css +0 -1
  236. writer/static/assets/BuilderModelSelect-ow0XEf2t.js +0 -7
  237. writer/static/assets/BuilderSettings-C2WRfSor.css +0 -1
  238. writer/static/assets/BuilderSettings-D5bjbcWj.js +0 -24
  239. writer/static/assets/BuilderSettingsHandlers-1QLaHR8J.css +0 -1
  240. writer/static/assets/BuilderSettingsHandlers-j1aMAV4J.js +0 -1
  241. writer/static/assets/BuilderSidebarComponentTree-0YajaJke.css +0 -1
  242. writer/static/assets/BuilderSidebarComponentTree-CtaOZfpV.js +0 -1
  243. writer/static/assets/BuilderSidebarPanel-BMJVzhd3.js +0 -1
  244. writer/static/assets/BuilderSidebarPanel-BrLsNxVM.css +0 -1
  245. writer/static/assets/BuilderSidebarToolkit-BbjOOp8E.js +0 -1
  246. writer/static/assets/BuilderSidebarToolkit-BvZDShKD.css +0 -1
  247. writer/static/assets/ComponentRenderer-B76bKRZO.css +0 -1
  248. writer/static/assets/ComponentRenderer-CZs4z773.js +0 -1
  249. writer/static/assets/SharedMoreDropdown-BWKlox8E.css +0 -1
  250. writer/static/assets/SharedMoreDropdown-DKv_HNef.js +0 -7
  251. writer/static/assets/WdsDropdownMenu-C1UyKOJR.css +0 -1
  252. writer/static/assets/WdsDropdownMenu-CGiATY2E.js +0 -1
  253. writer/static/assets/WdsLoaderDots-qdyk2N-2.js +0 -1
  254. writer/static/assets/index-BJMAe9SN.js +0 -8
  255. writer/static/assets/index-CPCeQU9V.css +0 -1
  256. writer/static/assets/index-ChWW_c_j.js +0 -439
  257. writer/static/assets/instancePath-BsbOTTI8.js +0 -1
  258. writer/static/assets/material-symbols-outlined-latin-wght-normal-DuE-q1Ez.woff2 +0 -0
  259. writer/static/assets/useBlueprintRun-CBOvzWTA.js +0 -1
  260. writer/static/assets/useComponentDescription-FHKxu8gg.js +0 -1
  261. writer/static/assets/useListResources-BpMgq7XI.js +0 -1
  262. writer-0.8.3rc21.dist-info/METADATA +0 -117
  263. writer-0.8.3rc21.dist-info/RECORD +0 -342
  264. {writer-0.8.3rc21.dist-info → writer-1.25.1rc1.dist-info}/entry_points.txt +0 -0
  265. {writer-0.8.3rc21.dist-info → writer-1.25.1rc1.dist-info/licenses}/LICENSE.txt +0 -0
writer/app_runner.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  import concurrent.futures
3
3
  import importlib.util
4
+ import io
4
5
  import logging
5
6
  import logging.handlers
6
7
  import multiprocessing
@@ -11,7 +12,9 @@ import shutil
11
12
  import signal
12
13
  import subprocess
13
14
  import sys
15
+ import tempfile
14
16
  import threading
17
+ import zipfile
15
18
  from types import ModuleType
16
19
  from typing import Any, Callable, Dict, List, Optional, Union, cast
17
20
 
@@ -19,7 +22,7 @@ import watchdog.events
19
22
  from pydantic import ValidationError
20
23
  from watchdog.observers.polling import PollingObserver
21
24
 
22
- from writer import VERSION, audit_and_fix, core_ui, crypto, wf_project
25
+ from writer import VERSION, audit_and_fix, core_ui, crypto, vault, wf_project
23
26
  from writer.core import (
24
27
  Config,
25
28
  EventHandlerRegistry,
@@ -28,6 +31,7 @@ from writer.core import (
28
31
  use_request_context,
29
32
  )
30
33
  from writer.core_ui import ingest_bmc_component_tree
34
+ from writer.logs import use_logging_redirect, use_stdout_redirect
31
35
  from writer.ss_types import (
32
36
  AppProcessServerRequest,
33
37
  AppProcessServerRequestPacket,
@@ -46,6 +50,7 @@ from writer.ss_types import (
46
50
  InitSessionResponsePayload,
47
51
  ListResourcesRequest,
48
52
  ListResourcesRequestPayload,
53
+ QueueMessageRequest,
49
54
  ServeMode,
50
55
  SourceFilesDirectory,
51
56
  StateContentRequest,
@@ -54,10 +59,11 @@ from writer.ss_types import (
54
59
  StateEnquiryResponsePayload,
55
60
  WriterApplicationInformation,
56
61
  WriterEvent,
62
+ WriterVaultUpdateRequest,
57
63
  )
58
64
  from writer.wf_project import WfProjectContext
59
65
 
60
- logging.basicConfig(level=logging.INFO, format="%(message)s")
66
+ user_code_logger = logging.getLogger("user_code")
61
67
 
62
68
 
63
69
  class MessageHandlingException(Exception):
@@ -172,13 +178,19 @@ class AppProcess(multiprocessing.Process):
172
178
  session.session_component_tree, mode=writer.Config.mode
173
179
  )
174
180
 
181
+ headers = session.headers or {}
175
182
  writer_application: Optional[WriterApplicationInformation] = None
176
- writer_app_id = os.getenv("WRITER_APP_ID")
177
- writer_org_id = os.getenv("WRITER_ORG_ID")
183
+ writer_app_id = headers.get("x-agent-id") or os.getenv("WRITER_APP_ID")
184
+ writer_org_id = headers.get("x-organization-id") or os.getenv("WRITER_ORG_ID")
185
+ writer_base_url = os.getenv("WRITER_BASE_URL", "https://api.writer.com")
178
186
  if writer_app_id is not None and writer_org_id is not None:
179
187
  writer_application = WriterApplicationInformation(
180
- id=writer_app_id, organizationId=writer_org_id
188
+ id=writer_app_id,
189
+ organizationId=writer_org_id,
190
+ baseUrl=writer_base_url
181
191
  )
192
+ if writer.Config.mode == "edit":
193
+ writer_application.apiKey = os.getenv("WRITER_API_KEY")
182
194
 
183
195
  res_payload = InitSessionResponsePayload(
184
196
  userState=user_state,
@@ -315,6 +327,7 @@ class AppProcess(multiprocessing.Process):
315
327
  "type": app.type,
316
328
  "status": app.status,
317
329
  "organization_id": organization_id,
330
+ "inputs": {i.name: "" for i in app.inputs},
318
331
  }
319
332
  for app in applications
320
333
  ]
@@ -402,6 +415,17 @@ class AppProcess(multiprocessing.Process):
402
415
  session.userinfo = request.payload
403
416
  return AppProcessServerResponse(status="ok", status_message=None, payload=None)
404
417
 
418
+ if type == "queueMessage":
419
+ session.queued_messages.append(request.payload)
420
+ return AppProcessServerResponse(status="ok", status_message=None, payload=None)
421
+
422
+ if type == "retrieveMessages":
423
+ return AppProcessServerResponse(status="ok", status_message=None, payload=session.queued_messages)
424
+
425
+ if type == "clearMessages":
426
+ session.queued_messages = []
427
+ return AppProcessServerResponse(status="ok", status_message=None, payload=None)
428
+
405
429
  if self.mode == "edit" and type == "hashRequest":
406
430
  hash_request_payload = HashRequestPayload.model_validate(request.payload)
407
431
  return AppProcessServerResponse(
@@ -419,6 +443,14 @@ class AppProcess(multiprocessing.Process):
419
443
  list_req_payload = ListResourcesRequestPayload.model_validate(request.payload)
420
444
  return self._handle_list_resources(session, list_req_payload)
421
445
 
446
+ if self.mode == "edit" and type == "writerVaultUpdate":
447
+ vault.writer_vault.refresh()
448
+ return AppProcessServerResponse(
449
+ status="ok",
450
+ status_message=None,
451
+ payload=None,
452
+ )
453
+
422
454
  raise MessageHandlingException("Invalid event.")
423
455
 
424
456
  def _execute_user_code(self) -> None:
@@ -427,7 +459,6 @@ class AppProcess(multiprocessing.Process):
427
459
  """
428
460
 
429
461
  import io
430
- from contextlib import redirect_stdout
431
462
 
432
463
  import writer
433
464
 
@@ -436,19 +467,65 @@ class AppProcess(multiprocessing.Process):
436
467
  raise ValueError("Couldn't find app module (writeruserapp).")
437
468
 
438
469
  code_path = os.path.join(self.app_path, "main.py")
439
- with redirect_stdout(io.StringIO()) as f:
440
- code = compile(self.run_code, code_path, "exec")
441
- exec(code, writeruserapp.__dict__)
442
- captured_stdout = f.getvalue()
443
-
444
- if captured_stdout:
445
- writer.core.initial_state.add_log_entry(
446
- "info", "Stdout message during initialization", captured_stdout
447
- )
470
+
471
+ # Containers to capture logs for KV storage
472
+ init_stdout_container = ['']
473
+ init_logs_container = ['']
474
+
475
+ try:
476
+ with (
477
+ use_stdout_redirect([
478
+ lambda entry: writer.core.initial_state.add_log_entry("info", "Stdout message during initialization", entry),
479
+ lambda entry: init_stdout_container.__setitem__(0, entry)
480
+ ]),
481
+ use_logging_redirect([
482
+ lambda entry: writer.core.initial_state.add_log_entry("info", "Logs during initialization", entry),
483
+ lambda entry: init_logs_container.__setitem__(0, entry)
484
+ ]),
485
+ ):
486
+ writeruserapp.__dict__["logger"] = user_code_logger
487
+ code = compile(self.run_code, code_path, "exec")
488
+ exec(code, writeruserapp.__dict__)
489
+ finally:
490
+ self._save_initialization_logs(init_stdout_container[0], init_logs_container[0])
448
491
 
449
492
  # Register non-private functions as handlers
450
493
  self.handler_registry.register_module(writeruserapp)
451
494
 
495
+ def _save_initialization_logs(self, stdout: str, logs: str) -> None:
496
+ """Save main.py initialization logs to KV storage."""
497
+ if not stdout and not logs:
498
+ return
499
+
500
+ from datetime import datetime, timezone
501
+
502
+ from writer.core import Config
503
+ from writer.journal import INIT_LOGS_KEY_PREFIX
504
+ from writer.keyvalue_storage import writer_kv_storage
505
+
506
+ if "journal" not in Config.feature_flags or not writer_kv_storage.is_accessible():
507
+ return
508
+
509
+ timestamp = datetime.now(timezone.utc)
510
+ # Match JournalRecord.instance_type logic: 'e' for editor, 'a' for agent
511
+ instance_type = "editor" if self.mode == "edit" else "agent"
512
+ instance_type_letter = instance_type[0] # 'e' or 'a'
513
+
514
+ key = f"{INIT_LOGS_KEY_PREFIX}{instance_type_letter}-{int(timestamp.timestamp() * 1000)}"
515
+ data = {
516
+ "timestamp": timestamp.isoformat(),
517
+ "instanceType": instance_type,
518
+ "mode": self.mode,
519
+ "stdout": stdout,
520
+ "logs": logs
521
+ }
522
+ try:
523
+ writer_kv_storage.save(key, data)
524
+ except Exception as e:
525
+ # Don't fail initialization if log saving fails
526
+ app_logger = logging.getLogger("app_runner")
527
+ app_logger.warning(f"Failed to save initialization logs to KV storage: {e}")
528
+
452
529
  def _apply_configuration(self) -> None:
453
530
  import writer
454
531
 
@@ -561,7 +638,7 @@ class AppProcess(multiprocessing.Process):
561
638
  pass
562
639
 
563
640
  self.is_app_process_server_ready.set()
564
- while True: # Starts app message server
641
+ while True and not is_app_process_server_terminated.is_set(): # Starts app message server
565
642
  try:
566
643
  if not self.server_conn.poll(1):
567
644
  continue
@@ -571,10 +648,7 @@ class AppProcess(multiprocessing.Process):
571
648
  terminate_server()
572
649
  return
573
650
  self._handle_app_process_server_packet(packet)
574
- except InterruptedError:
575
- terminate_server()
576
- return
577
- except BaseException as e:
651
+ except Exception as e:
578
652
  self.logger.error(f"Unexpected exception in AppProcess server.\n{repr(e)}")
579
653
  terminate_server()
580
654
  return
@@ -589,8 +663,9 @@ class AppProcess(multiprocessing.Process):
589
663
  thread_pool_future.add_done_callback(self._send_packet)
590
664
 
591
665
  def run(self) -> None:
666
+ max_workers = int(os.getenv("WRITER_MAX_WORKERS", (os.cpu_count() or 4) * 10))
592
667
  self.executor = concurrent.futures.ThreadPoolExecutor(
593
- max_workers=(os.cpu_count() or 4) * 10
668
+ max_workers=max_workers,
594
669
  )
595
670
  self.server_conn_lock = threading.Lock()
596
671
  self.client_conn.close()
@@ -680,8 +755,6 @@ class LogListener(threading.Thread):
680
755
  super().__init__(name="LogListenerThread")
681
756
  self.log_queue = log_queue
682
757
  self.logger = logging.getLogger("from_app")
683
- self.logger.setLevel(logging.INFO)
684
- self.logger.addHandler(logging.StreamHandler())
685
758
 
686
759
  def run(self) -> None:
687
760
  while True:
@@ -738,23 +811,26 @@ class AppRunner:
738
811
  self.serve_loop = asyncio.get_running_loop()
739
812
 
740
813
  def _set_logger(self):
741
- logger = logging.getLogger("app")
814
+ logger = logging.getLogger("app_runner")
742
815
  logger.addHandler(logging.handlers.QueueHandler(self.log_queue))
743
816
  self.log_listener = LogListener(self.log_queue)
744
817
  self.log_listener.start()
745
818
 
746
819
  def _start_fs_observer(self):
747
- self.observer = PollingObserver(AppRunner.UPDATE_CHECK_INTERVAL_SECONDS)
820
+ if self.observer is None:
821
+ self.observer = PollingObserver(AppRunner.UPDATE_CHECK_INTERVAL_SECONDS)
748
822
  self.observer.schedule(
749
823
  FileEventHandler(self.reload_code_from_saved, patterns=["*.py"]),
750
824
  path=self.app_path,
751
825
  recursive=True,
752
826
  )
753
- self.observer.schedule(
754
- FileEventHandler(self._install_requirements, patterns=["requirements.txt"]),
755
- path=self.app_path,
756
- )
757
- self.observer.start()
827
+ # See _install_requirements docstring for info
828
+ # self.observer.schedule(
829
+ # FileEventHandler(self._install_requirements, patterns=["requirements.txt"]),
830
+ # path=self.app_path,
831
+ # )
832
+ if not self.observer.is_alive():
833
+ self.observer.start()
758
834
 
759
835
  def _start_wf_project_process_write_files(self):
760
836
  wf_project.start_process_write_files_async(
@@ -762,6 +838,11 @@ class AppRunner:
762
838
  )
763
839
 
764
840
  def _install_requirements(self) -> None:
841
+ """
842
+ Not used anywhere anymore as this method of installing dependencies is not supported.
843
+ Left because might change in the future.
844
+ """
845
+
765
846
  logger = logging.getLogger("writer")
766
847
  logger.debug("\nDetected changes in requirements.txt. Installing dependencies...")
767
848
  try:
@@ -918,8 +999,13 @@ class AppRunner:
918
999
  raise error
919
1000
 
920
1001
  def _check_file_in_app_path(self, path):
921
- if not os.path.abspath(path).startswith(os.path.abspath((self.app_path))):
922
- raise PermissionError(f"{path} is outside of application ({self.app_path})")
1002
+ app_path = os.path.abspath(self.app_path)
1003
+ file_path = os.path.abspath(path)
1004
+ if file_path == app_path or not file_path.startswith(app_path):
1005
+ raise PermissionError(f"{path} should be inside of application ({self.app_path})")
1006
+ wf_path = os.path.abspath(os.path.join(self.app_path, ".wf"))
1007
+ if file_path.startswith(wf_path):
1008
+ raise PermissionError(f"{path} should not be inside of Writer Framework files ({wf_path})")
923
1009
 
924
1010
  def _load_persisted_components(self) -> Dict[str, ComponentDefinition]:
925
1011
  logger = logging.getLogger("writer")
@@ -937,6 +1023,23 @@ class AppRunner:
937
1023
  components = audit_and_fix.fix_components(components)
938
1024
  return components
939
1025
 
1026
+ async def queue_message(self, session_id: str, data: Any) -> AppProcessServerResponse:
1027
+ return await self.dispatch_message(session_id, QueueMessageRequest(type="queueMessage", payload=data))
1028
+
1029
+ async def retrieve_messages(self, session_id: str) -> list:
1030
+ response = await self.dispatch_message(
1031
+ session_id, AppProcessServerRequest(type="retrieveMessages", payload=None)
1032
+ )
1033
+ if isinstance(response.payload, list):
1034
+ return response.payload
1035
+ return []
1036
+
1037
+ async def clear_messages(self, session_id: str) -> AppProcessServerResponse:
1038
+ response = await self.dispatch_message(
1039
+ session_id, AppProcessServerRequest(type="clearMessages", payload=None)
1040
+ )
1041
+ return response
1042
+
940
1043
  async def check_session(self, session_id: str) -> bool:
941
1044
  response = await self.dispatch_message(
942
1045
  session_id, AppProcessServerRequest(type="checkSession", payload=None)
@@ -973,6 +1076,10 @@ class AppRunner:
973
1076
  message = ListResourcesRequest(type="listResources", payload=message_payload)
974
1077
  return await self.dispatch_message(session_id, message)
975
1078
 
1079
+ async def writer_vault_refresh(self, session_id: str) -> AppProcessServerResponse:
1080
+ message = WriterVaultUpdateRequest(type="writerVaultUpdate")
1081
+ return await self.dispatch_message(session_id, message)
1082
+
976
1083
  async def handle_event(self, session_id: str, event: WriterEvent) -> AppProcessServerResponse:
977
1084
  return await self.dispatch_message(session_id, EventRequest(type="event", payload=event))
978
1085
 
@@ -1011,6 +1118,103 @@ class AppRunner:
1011
1118
 
1012
1119
  self.source_files = wf_project.build_source_files(self.app_path)
1013
1120
 
1121
+ def export_zip(self):
1122
+ if self.mode != "edit":
1123
+ raise PermissionError("Cannot export in non-edit mode.")
1124
+ zip_buffer = io.BytesIO()
1125
+ with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zipf:
1126
+ for root, dirs, files in os.walk(self.app_path):
1127
+ for file in files:
1128
+ if file.endswith('.pyc'):
1129
+ continue
1130
+ full_path = os.path.join(root, file)
1131
+ arcname = os.path.relpath(full_path, start=self.app_path)
1132
+ zipf.write(full_path, arcname=arcname)
1133
+ zip_buffer.seek(0)
1134
+ return zip_buffer
1135
+
1136
+ def _sync_folders(self, src: str, dst: str):
1137
+ """
1138
+ Synchronizes the contents of the source folder to the destination folder in a one-way manner.
1139
+
1140
+ - Copies all files and subdirectories from `src` to `dst`.
1141
+ - Creates any missing directories in `dst` to match `src`.
1142
+ - Removes any files or directories in `dst` that do not exist in `src`.
1143
+ """
1144
+ # Create dst if it doesn't exist
1145
+ os.makedirs(dst, exist_ok=True)
1146
+
1147
+ # Copy files and folders from src to dst
1148
+ for root, dirs, files in os.walk(src):
1149
+ rel_path = os.path.relpath(root, src)
1150
+ dst_path = os.path.join(dst, rel_path)
1151
+
1152
+ # Create directories in dst
1153
+ os.makedirs(dst_path, exist_ok=True)
1154
+
1155
+ # Copy files
1156
+ for file in files:
1157
+ src_file = os.path.join(root, file)
1158
+ dst_file = os.path.join(dst_path, file)
1159
+ shutil.copy2(src_file, dst_file)
1160
+
1161
+ # Remove files and folders in dst that don't exist in src
1162
+ for root, dirs, files in os.walk(dst):
1163
+ rel_path = os.path.relpath(root, dst)
1164
+ src_path = os.path.join(src, rel_path)
1165
+
1166
+ # Remove files not in src
1167
+ for file in files:
1168
+ dst_file = os.path.join(root, file)
1169
+ src_file = os.path.join(src_path, file)
1170
+ if not os.path.exists(src_file):
1171
+ os.remove(dst_file)
1172
+
1173
+ # Remove empty directories not in src
1174
+ for dir in dirs:
1175
+ dst_dir = os.path.join(root, dir)
1176
+ src_dir = os.path.join(src_path, dir)
1177
+ if not os.path.exists(src_dir):
1178
+ shutil.rmtree(dst_dir)
1179
+
1180
+ async def import_zip(self, zip_path: str):
1181
+ if self.mode != "edit":
1182
+ raise PermissionError("Cannot import in non-edit mode.")
1183
+
1184
+ try:
1185
+ with tempfile.TemporaryDirectory() as tmpdir:
1186
+ extracted_path = os.path.join(tmpdir, "imported_agent")
1187
+ os.makedirs(extracted_path, exist_ok=True)
1188
+ with zipfile.ZipFile(zip_path, "r") as zip_ref:
1189
+ zip_ref.extractall(extracted_path)
1190
+
1191
+ main_py_dir = None
1192
+ for root, _, files in os.walk(extracted_path):
1193
+ if "main.py" in files:
1194
+ main_py_dir = root
1195
+ break
1196
+
1197
+ if main_py_dir is None:
1198
+ raise ValueError("main.py not found in the imported archive.")
1199
+
1200
+ wf_dir_path = os.path.join(main_py_dir, ".wf")
1201
+ if not os.path.isdir(wf_dir_path):
1202
+ raise ValueError(".wf directory not found alongside main.py in the archive.")
1203
+
1204
+ # Passed all checks; replace current app contents
1205
+
1206
+ logging.info("Copying app at %s", main_py_dir)
1207
+ if self.observer is not None:
1208
+ self.observer.unschedule_all()
1209
+
1210
+ self._sync_folders(main_py_dir, self.app_path)
1211
+
1212
+ self._start_fs_observer()
1213
+ self.bmc_components = self._load_persisted_components()
1214
+ self.reload_code_from_saved()
1215
+ except zipfile.BadZipFile:
1216
+ raise ValueError("Uploaded file is not a valid ZIP.")
1217
+
1014
1218
  def _clean_process(self) -> None:
1015
1219
  # Terminate the AppProcess server by sending an empty message
1016
1220
  # The empty message will bounce an empty message and terminate the client too
@@ -0,0 +1,11 @@
1
+ {"id": "0decp3w5erhvl0nw", "type": "blueprints_blueprint", "content": {"key": "button@click_1"}, "handlers": {}, "isCodeManaged": false, "parentId": "blueprints_root", "position": 0}
2
+ {"id": "dv1jzdoaz27j60wq", "type": "blueprints_uieventtrigger", "content": {"alias": "Draft response clicked", "refComponentId": "zazp9q0cpsglynsb", "refEventType": "wf-click"}, "handlers": {}, "isCodeManaged": false, "outs": [{"toNodeId": "nxwa9ls502oqhy9u", "outId": "trigger"}], "parentId": "0decp3w5erhvl0nw", "position": 0, "x": 72, "y": 624}
3
+ {"id": "nxwa9ls502oqhy9u", "type": "blueprints_setstate", "content": {"alias": "Set progress message", "element": "progress_message", "value": "% Reviewing..."}, "handlers": {}, "isCodeManaged": false, "outs": [{"toNodeId": "r0314ycb8wb21pc8", "outId": "success"}], "parentId": "0decp3w5erhvl0nw", "position": 1, "x": 456, "y": 576}
4
+ {"id": "r0314ycb8wb21pc8", "type": "blueprints_writerclassification", "content": {"alias": "Classify review category", "categories": "{\n \"Packaging\": \"The review mentions packaging issues or compliments.\",\n \"Pricing\": \"The review discusses pricing concerns or satisfaction.\",\n \"Quality\": \"The review is about the quality of the product or service.\",\n \"Delivery\": \"The review relates to delivery times or issues.\",\n \"Empty\": \"A review is mentioned but the review cannot be found\"\n}", "text": "The review ---- @{customer_review}"}, "handlers": {}, "isCodeManaged": false, "outs": [{"toNodeId": "fmltu4qrq9qnu7vk", "outId": "category_Packaging"}, {"toNodeId": "n63w3bvyin80gus4", "outId": "category_Pricing"}, {"toNodeId": "uolelullwetqhgbv", "outId": "category_Quality"}, {"toNodeId": "fyvf1qtrpq85vac3", "outId": "category_Delivery"}, {"toNodeId": "2goghkw7qirzs9u9", "outId": "category_Empty"}], "parentId": "0decp3w5erhvl0nw", "position": 2, "x": 840, "y": 504}
5
+ {"id": "fmltu4qrq9qnu7vk", "type": "blueprints_writercompletion", "content": {"alias": "Draft packaging response", "modelId": "palmyra-x5", "prompt": "Take the role of a customer success rep and draft an email response to the customer review below that mentions packaging: @{customer_review}\n\nThe response should be short, positive and helpful in style.\n\nAcknowledge their feedback about the packaging. Express appreciation if it\u2019s positive, or apologize and offer a resolution if it\u2019s negative. Mention steps we're taking to improve or maintain packaging standards."}, "handlers": {}, "isCodeManaged": false, "outs": [{"toNodeId": "253p2vp9msfuywdo", "outId": "success"}], "parentId": "0decp3w5erhvl0nw", "position": 3, "x": 1224, "y": 96}
6
+ {"id": "n63w3bvyin80gus4", "type": "blueprints_writercompletion", "content": {"alias": "Draft pricing response", "modelId": "palmyra-x5", "prompt": "Take the role of a customer success rep and draft an email response to the customer review below that mentions pricing: @{customer_review}\n\nRespond to the customer\u2019s comments on pricing. If it\u2019s positive, thank them for recognizing the value. If it\u2019s negative, empathize and briefly explain our pricing approach or any ongoing offers that may help."}, "handlers": {}, "isCodeManaged": false, "outs": [{"toNodeId": "253p2vp9msfuywdo", "outId": "success"}], "parentId": "0decp3w5erhvl0nw", "position": 4, "x": 1224, "y": 360}
7
+ {"id": "uolelullwetqhgbv", "type": "blueprints_writercompletion", "content": {"alias": "Draft quality response", "modelId": "palmyra-x5", "prompt": "Take the role of a customer success rep and draft an email response to the customer review below that mentions quality: @{customer_review}\n\nCraft a response focused on product quality. Thank the reviewer if they\u2019re satisfied. If not, apologize sincerely and offer help or a replacement, while reinforcing our commitment to high standards."}, "handlers": {}, "isCodeManaged": false, "outs": [{"toNodeId": "253p2vp9msfuywdo", "outId": "success"}], "parentId": "0decp3w5erhvl0nw", "position": 5, "x": 1224, "y": 576}
8
+ {"id": "fyvf1qtrpq85vac3", "type": "blueprints_writercompletion", "content": {"alias": "Draft delivery response", "modelId": "palmyra-x5", "prompt": "Take the role of a customer success rep and draft an email response to the customer review below that mentions delivery: @{customer_review}\n\nAddress the customer\u2019s delivery experience. Thank them if it was smooth, or apologize and investigate if it was delayed or problematic. Reassure them we\u2019re improving logistics and care about their satisfaction."}, "handlers": {}, "isCodeManaged": false, "outs": [{"toNodeId": "253p2vp9msfuywdo", "outId": "success"}], "parentId": "0decp3w5erhvl0nw", "position": 6, "x": 1224, "y": 816}
9
+ {"id": "2goghkw7qirzs9u9", "type": "blueprints_setstate", "content": {"alias": "Handle empty review", "element": "review_response", "value": "You must specify a review."}, "handlers": {}, "isCodeManaged": false, "outs": [{"toNodeId": "253p2vp9msfuywdo", "outId": "success"}], "parentId": "0decp3w5erhvl0nw", "position": 7, "x": 1224, "y": 1056}
10
+ {"id": "253p2vp9msfuywdo", "type": "blueprints_setstate", "content": {"alias": "Store response", "element": "review_response", "value": "@{result}"}, "handlers": {}, "isCodeManaged": false, "outs": [{"toNodeId": "x4flvjxp3vcyz21s", "outId": "success"}], "parentId": "0decp3w5erhvl0nw", "position": 8, "x": 1608, "y": 576}
11
+ {"id": "x4flvjxp3vcyz21s", "type": "blueprints_setstate", "content": {"alias": "Clear progress message", "element": "progress_message"}, "handlers": {}, "isCodeManaged": false, "parentId": "0decp3w5erhvl0nw", "position": 9, "x": 1992, "y": 576}
@@ -1,15 +1,27 @@
1
1
  {"id": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "type": "page", "content": {"pageMode": "compact"}, "handlers": {}, "isCodeManaged": false, "parentId": "root", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
2
- {"id": "vwrfbi2qot5clwzy", "type": "image", "content": {"caption": "", "src": "static/welcome.svg?3"}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 0}
3
- {"id": "29qur7xljy4faoqt", "type": "section", "content": {"title": "Example customer review responder"}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 1}
4
- {"id": "fkxnuw7eitb422j6", "type": "text", "content": {"text": "See how a customer review can be categorized and used to generate a personalized response.\nCopy the sample review below, paste it in the customer review text box, and click draft response to see it in action."}, "handlers": {}, "isCodeManaged": false, "parentId": "29qur7xljy4faoqt", "position": 0}
5
- {"id": "jk7fvn9aek99d5hs", "type": "section", "content": {"containerBackgroundColor": "#D4FFF2", "containerShadow": "none", "separatorColor": "none", "title": ""}, "handlers": {}, "isCodeManaged": false, "parentId": "29qur7xljy4faoqt", "position": 1}
6
- {"id": "el6v2mozugelu9mw", "type": "text", "content": {"text": "**1. Copy me**\n\n\u201cI\u2019ve been searching for the perfect tailored blazer for years and I think I\u2019ve finally found it. The cut is precise without feeling restrictive. the material is rich and smooth\u201d", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "jk7fvn9aek99d5hs", "position": 0}
7
- {"id": "po6m73qliiv7g2ep", "type": "textareainput", "binding": {"eventType": "wf-change", "stateRef": "customer_review"}, "content": {"label": "2. Paste the example customer review above", "rows": "5"}, "handlers": {}, "isCodeManaged": false, "parentId": "29qur7xljy4faoqt", "position": 2}
8
- {"id": "zazp9q0cpsglynsb", "type": "button", "content": {"text": "3. Generate response"}, "handlers": {}, "isCodeManaged": false, "parentId": "29qur7xljy4faoqt", "position": 3}
9
- {"id": "k9fn5wf1kqwxvugj", "type": "section", "content": {"title": "Response message"}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 2, "visible": {"binding": "", "expression": "custom", "reversed": false}}
2
+ {"id": "2ime7p1jioyr1yz2", "type": "sidebar", "content": {"sidebarBackgroundColor": "#ECE1FF"}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": -2, "visible": {"binding": "", "expression": true, "reversed": false}}
3
+ {"id": "3toyuvfwhlwsq8e6", "type": "tags", "content": {"primaryTextColor": "#ffffff", "referenceColor": "#4A46DA", "rotateHue": "no", "tags": "{\n \"Public Beta\": \"Public Beta\"\n}"}, "handlers": {}, "isCodeManaged": false, "parentId": "2ime7p1jioyr1yz2", "position": 0}
4
+ {"id": "pb3ooumyznopaqh4", "type": "heading", "content": {"headingType": "h1", "text": "Welcome to Agent Builder"}, "handlers": {}, "isCodeManaged": false, "parentId": "2ime7p1jioyr1yz2", "position": 1}
5
+ {"id": "a4rbvwms5exs3bta", "type": "text", "content": {"text": "A collaborative platform for engineers and business users to design, build and deploy AI agents together."}, "handlers": {}, "isCodeManaged": false, "parentId": "2ime7p1jioyr1yz2", "position": 2}
6
+ {"id": "y2mrnml4ukza7uwd", "type": "image", "content": {"caption": "", "maxWidth": "160px", "src": "static/agent_builder_demo.png"}, "handlers": {}, "isCodeManaged": false, "parentId": "2ime7p1jioyr1yz2", "position": 3}
7
+ {"id": "29qur7xljy4faoqt", "type": "section", "content": {"title": ""}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 0}
8
+ {"id": "7fz70dyp64raqr7u", "type": "heading", "content": {"headingType": "h1", "text": "Example Customer Support Agent "}, "handlers": {}, "isCodeManaged": false, "parentId": "29qur7xljy4faoqt", "position": 0}
9
+ {"id": "fkxnuw7eitb422j6", "type": "text", "content": {"text": "Test this agent in Preview and then go to Interface and Blueprints to see how it was built. ", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "29qur7xljy4faoqt", "position": 1}
10
+ {"id": "5dgdzkdrgc5a9yss", "type": "separator", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "29qur7xljy4faoqt", "position": 2}
11
+ {"id": "iee6l0lcbz2cne8q", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "29qur7xljy4faoqt", "position": 3}
12
+ {"id": "cqkp4hx6rjurlrhd", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "iee6l0lcbz2cne8q", "position": 0}
13
+ {"id": "2mybry5io5mmww7x", "type": "section", "content": {"containerBackgroundColor": "#fcfcfc", "title": ""}, "handlers": {}, "isCodeManaged": false, "parentId": "cqkp4hx6rjurlrhd", "position": 0}
14
+ {"id": "f52d0p6b65rjqkpx", "type": "heading", "content": {"headingType": "h3", "text": "1. Copy Review"}, "handlers": {}, "isCodeManaged": false, "parentId": "2mybry5io5mmww7x", "position": 0}
15
+ {"id": "el6v2mozugelu9mw", "type": "text", "content": {"primaryTextColor": "", "text": "*I\u2019ve been searching for the perfect tailored blazer for years and I think I\u2019ve finally found it. The cut is precise without feeling restrictive. The material is rich and smooth. It instantly elevates any outfit and makes me feel confident the moment I put it on. I've already received several compliments after just one wear.*", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "2mybry5io5mmww7x", "position": 1}
16
+ {"id": "gzl9y3qtexw7vmwg", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "iee6l0lcbz2cne8q", "position": 1}
17
+ {"id": "p7golp2r05uziezm", "type": "section", "content": {"containerBackgroundColor": "#fcfcfc", "title": ""}, "handlers": {}, "isCodeManaged": false, "parentId": "gzl9y3qtexw7vmwg", "position": 0}
18
+ {"id": "8kzm2mhcv5xd5ji2", "type": "heading", "content": {"text": "2. Paste Review"}, "handlers": {}, "isCodeManaged": false, "parentId": "p7golp2r05uziezm", "position": 0}
19
+ {"id": "po6m73qliiv7g2ep", "type": "textareainput", "binding": {"eventType": "wf-change", "stateRef": "customer_review"}, "content": {"label": "", "rows": "4"}, "handlers": {}, "isCodeManaged": false, "parentId": "p7golp2r05uziezm", "position": 1}
20
+ {"id": "zazp9q0cpsglynsb", "type": "button", "content": {"text": "3. Draft response"}, "handlers": {}, "isCodeManaged": false, "parentId": "29qur7xljy4faoqt", "position": 4}
21
+ {"id": "k9fn5wf1kqwxvugj", "type": "section", "content": {"title": "Response message"}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 1, "visible": {"binding": "", "expression": "custom", "reversed": false}}
10
22
  {"id": "khbe69j233n3d9ot", "type": "message", "content": {"message": "@{progress_message}"}, "handlers": {}, "isCodeManaged": false, "parentId": "k9fn5wf1kqwxvugj", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
11
23
  {"id": "z2noh2qiejykjjlq", "type": "section", "content": {"containerBackgroundColor": "#F5F5F9", "containerShadow": "none", "separatorColor": "none", "title": ""}, "handlers": {}, "isCodeManaged": false, "parentId": "k9fn5wf1kqwxvugj", "position": 1, "visible": {"binding": "review_response", "expression": "custom", "reversed": false}}
12
24
  {"id": "sbzcwvq5e0z386cq", "type": "text", "content": {"text": "@{review_response}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "z2noh2qiejykjjlq", "position": 0, "visible": {"binding": "", "expression": "custom", "reversed": false}}
13
- {"id": "coz1oojtyzs0slm0", "type": "section", "content": {"containerBackgroundColor": "#F5F5F9", "containerShadow": "none", "separatorColor": "none", "title": ""}, "handlers": {}, "isCodeManaged": false, "parentId": "k9fn5wf1kqwxvugj", "position": 2, "visible": {"binding": "review_response", "expression": "custom", "reversed": true}}
25
+ {"id": "coz1oojtyzs0slm0", "type": "section", "content": {"containerBackgroundColor": "#fcfcfc", "containerShadow": "none", "separatorColor": "none", "title": ""}, "handlers": {}, "isCodeManaged": false, "parentId": "k9fn5wf1kqwxvugj", "position": 2, "visible": {"binding": "review_response", "expression": "custom", "reversed": true}}
14
26
  {"id": "akkcjdnrtudjd704", "type": "text", "content": {"text": "The response will be shown here.", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "coz1oojtyzs0slm0", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
15
- {"id": "nbr03edevbgnhi29", "type": "text", "content": {"alignment": "center", "primaryTextColor": "#828282", "text": "This is just an example agent - edit or delete it to start experimenting."}, "handlers": {}, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 3}
27
+ {"id": "nbr03edevbgnhi29", "type": "text", "content": {"alignment": "center", "primaryTextColor": "#828282", "text": "This is just an example agent - just delete the page and create a new one to start experimenting."}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 2}
@@ -1 +1 @@
1
- {"id": "root", "type": "root", "content": {"appName": "My App"}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
1
+ {"id": "root", "type": "root", "content": {"appName": "lucky-jellyfish"}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
@@ -0,0 +1 @@
1
+ {"id": "workflows_root", "type": "workflows_root", "content": {}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
@@ -0,0 +1 @@
1
+ {"id": "lfltcky7l1fsm6j2", "type": "workflows_workflow", "content": {}, "handlers": {}, "parentId": "workflows_root", "position": 0}
@@ -1,3 +1,3 @@
1
1
  {
2
- "writer_version": "0.8.3rc12"
2
+ "writer_version": "0.8.3rc21"
3
3
  }
@@ -0,0 +1,3 @@
1
+ This app was created using Writer Framework.
2
+
3
+ To learn more about it, visit https://dev.writer.com/framework
@@ -3,11 +3,14 @@ import writer as wf
3
3
  # Shows in the log when the app starts
4
4
  # print("Hello world!")
5
5
 
6
+ # Or you can use a logger object
7
+ # logger.info("Successful startup")
8
+
6
9
  # You can define functions which can be called from Python code blocks
7
- def my_func():
8
- return 1
10
+ # def my_func():
11
+ # return 1
9
12
 
10
13
  # You can initialize state via code
11
- initial_state = wf.init_state({
12
- "my_var": 1337,
13
- })
14
+ # initial_state = wf.init_state({
15
+ # "my_var": 1337,
16
+ # })
@@ -0,0 +1 @@
1
+ writer==0.8.3rc21
@@ -3,6 +3,9 @@ import writer as wf
3
3
  # Shows in the log when the app starts
4
4
  # print("Hello world!")
5
5
 
6
+ # Or you can use a logger object
7
+ # logger.info("Successful startup")
8
+
6
9
  # You can define functions which can be called from Python code blocks
7
10
  def my_func():
8
11
  return 1
writer/auth.py CHANGED
@@ -82,7 +82,7 @@ class BasicAuth(Auth):
82
82
  >>> login=os.getenv('LOGIN'),
83
83
  >>> password=os.getenv('PASSWORD'),
84
84
  >>> delay_after_failure=5,
85
- >>> block_webserver_after_failure=False
85
+ >>> block_user_after_failure=False
86
86
  >>> )
87
87
  """
88
88
  login: str
@@ -405,6 +405,11 @@ def _client_ip(request: Request) -> str:
405
405
  ip = x_forwarded_for.split(",")[0].strip()
406
406
  else:
407
407
  # Otherwise, use the direct connection IP
408
- ip = request.headers.get("X-Real-IP", request.client.host) # type: ignore
408
+ x_real_ip = request.headers.get("X-Real-IP")
409
+ if x_real_ip is not None:
410
+ ip = x_real_ip
411
+ else:
412
+ client = request.client
413
+ ip = client.host if client is not None else ""
409
414
 
410
415
  return ip