@lazyingart/agintiflow 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/styles.css CHANGED
@@ -14,6 +14,13 @@
14
14
  box-sizing: border-box;
15
15
  }
16
16
 
17
+ html,
18
+ body {
19
+ width: 100%;
20
+ max-width: 100%;
21
+ overflow-x: hidden;
22
+ }
23
+
17
24
  body {
18
25
  margin: 0;
19
26
  font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
@@ -25,20 +32,24 @@ body {
25
32
  }
26
33
 
27
34
  .page {
28
- width: min(1240px, calc(100% - 32px));
35
+ width: min(1240px, 100%);
36
+ max-width: 100%;
29
37
  margin: 32px auto;
38
+ padding: 0 16px;
30
39
  display: grid;
31
40
  gap: 20px;
32
- grid-template-columns: minmax(320px, 420px) minmax(0, 1fr);
41
+ grid-template-columns: minmax(280px, 400px) minmax(0, 1fr);
33
42
  }
34
43
 
35
44
  .stack {
36
45
  display: grid;
46
+ min-width: 0;
37
47
  align-content: start;
38
48
  gap: 14px;
39
49
  }
40
50
 
41
51
  .panel {
52
+ min-width: 0;
42
53
  background: var(--panel);
43
54
  border: 1px solid var(--line);
44
55
  border-radius: 18px;
@@ -109,6 +120,27 @@ h1 {
109
120
  line-height: 1.45;
110
121
  }
111
122
 
123
+ .project-card,
124
+ .setup-card {
125
+ display: grid;
126
+ min-width: 0;
127
+ gap: 10px;
128
+ margin: 14px 0;
129
+ padding: 13px;
130
+ border: 1px solid rgba(15, 118, 110, 0.18);
131
+ border-radius: 16px;
132
+ background: linear-gradient(135deg, rgba(204, 251, 241, 0.44), rgba(255, 247, 237, 0.9));
133
+ }
134
+
135
+ .project-card p,
136
+ .setup-card p {
137
+ overflow-wrap: anywhere;
138
+ }
139
+
140
+ .setup-card[hidden] {
141
+ display: none;
142
+ }
143
+
112
144
  form,
113
145
  .checks {
114
146
  display: grid;
@@ -137,6 +169,7 @@ button {
137
169
  input,
138
170
  textarea,
139
171
  select {
172
+ min-width: 0;
140
173
  width: 100%;
141
174
  border: 1px solid var(--line);
142
175
  background: #fffdfa;
@@ -306,6 +339,8 @@ button.danger {
306
339
  .actions,
307
340
  .header-row {
308
341
  display: flex;
342
+ min-width: 0;
343
+ flex-wrap: wrap;
309
344
  gap: 12px;
310
345
  align-items: center;
311
346
  justify-content: space-between;
@@ -417,6 +452,7 @@ button.danger {
417
452
  font-size: 0.72rem;
418
453
  line-height: 1.42;
419
454
  white-space: pre-wrap;
455
+ overflow-wrap: anywhere;
420
456
  }
421
457
 
422
458
  .sandbox-card h2 {
@@ -444,6 +480,7 @@ button.danger {
444
480
  font-size: 0.78rem;
445
481
  line-height: 1.45;
446
482
  white-space: pre-wrap;
483
+ overflow-wrap: anywhere;
447
484
  }
448
485
 
449
486
  .session-select {
@@ -452,13 +489,18 @@ button.danger {
452
489
 
453
490
  .session-tools {
454
491
  display: flex;
455
- min-width: min(100%, 420px);
492
+ flex: 1 1 420px;
493
+ min-width: 0;
494
+ max-width: 100%;
495
+ flex-wrap: wrap;
456
496
  gap: 8px;
457
497
  align-items: center;
458
498
  justify-content: flex-end;
459
499
  }
460
500
 
461
501
  .session-tools .session-select {
502
+ flex: 1 1 220px;
503
+ min-width: 0;
462
504
  max-width: none;
463
505
  }
464
506
 
@@ -476,6 +518,7 @@ button.danger {
476
518
  font-size: 0.9rem;
477
519
  line-height: 1.5;
478
520
  white-space: pre-wrap;
521
+ overflow-wrap: anywhere;
479
522
  }
480
523
 
481
524
  .chat-panel {
@@ -514,6 +557,7 @@ button.danger {
514
557
 
515
558
  .chat-content {
516
559
  line-height: 1.5;
560
+ overflow-wrap: anywhere;
517
561
  }
518
562
 
519
563
  .chat-form {
@@ -522,6 +566,7 @@ button.danger {
522
566
 
523
567
  .modal {
524
568
  width: min(720px, calc(100% - 28px));
569
+ max-width: 100%;
525
570
  padding: 0;
526
571
  border: 0;
527
572
  border-radius: 22px;
@@ -604,6 +649,7 @@ button.danger {
604
649
 
605
650
  .artifact-modal {
606
651
  width: min(1120px, calc(100% - 28px));
652
+ max-width: 100%;
607
653
  }
608
654
 
609
655
  .artifact-modal-card {
@@ -613,6 +659,7 @@ button.danger {
613
659
 
614
660
  .artifact-tabs {
615
661
  display: flex;
662
+ max-width: 100%;
616
663
  flex-wrap: wrap;
617
664
  gap: 8px;
618
665
  padding: 5px;
@@ -623,7 +670,7 @@ button.danger {
623
670
 
624
671
  .artifact-tab {
625
672
  flex: 1;
626
- min-width: 130px;
673
+ min-width: min(130px, 100%);
627
674
  border: 1px solid transparent;
628
675
  background: transparent;
629
676
  color: var(--muted);
@@ -639,6 +686,7 @@ button.danger {
639
686
  .artifact-layout {
640
687
  display: grid;
641
688
  min-height: 520px;
689
+ max-width: 100%;
642
690
  gap: 14px;
643
691
  grid-template-columns: minmax(240px, 340px) minmax(0, 1fr);
644
692
  overflow: hidden;
@@ -674,6 +722,7 @@ button.danger {
674
722
  .artifact-list {
675
723
  display: grid;
676
724
  align-content: start;
725
+ min-width: 0;
677
726
  gap: 8px;
678
727
  overflow: auto;
679
728
  padding: 10px;
@@ -681,7 +730,8 @@ button.danger {
681
730
 
682
731
  .artifact-row {
683
732
  display: grid;
684
- gap: 5px;
733
+ grid-template-columns: minmax(0, 1fr) auto;
734
+ gap: 8px;
685
735
  width: 100%;
686
736
  padding: 11px 12px;
687
737
  border: 1px solid var(--line);
@@ -691,6 +741,40 @@ button.danger {
691
741
  text-align: left;
692
742
  }
693
743
 
744
+ .artifact-row-main {
745
+ display: grid;
746
+ min-width: 0;
747
+ gap: 5px;
748
+ padding: 0;
749
+ border: 0;
750
+ border-radius: 0;
751
+ background: transparent;
752
+ color: inherit;
753
+ box-shadow: none;
754
+ text-align: left;
755
+ }
756
+
757
+ .artifact-menu-button {
758
+ width: 34px;
759
+ min-width: 34px;
760
+ height: 34px;
761
+ align-self: start;
762
+ padding: 0;
763
+ border: 1px solid var(--line);
764
+ border-radius: 999px;
765
+ background: rgba(255, 253, 250, 0.9);
766
+ color: var(--muted);
767
+ font-weight: 900;
768
+ letter-spacing: 0.08em;
769
+ box-shadow: none;
770
+ }
771
+
772
+ .artifact-menu-button:hover,
773
+ .artifact-menu-button:focus-visible {
774
+ border-color: rgba(15, 118, 110, 0.34);
775
+ color: var(--accent);
776
+ }
777
+
694
778
  .artifact-row[data-selected="true"] {
695
779
  border-color: rgba(15, 118, 110, 0.62);
696
780
  box-shadow: 0 0 0 2px rgba(15, 118, 110, 0.1);
@@ -715,6 +799,7 @@ button.danger {
715
799
  .artifact-row-top,
716
800
  .artifact-viewer-header {
717
801
  display: flex;
802
+ min-width: 0;
718
803
  gap: 10px;
719
804
  align-items: flex-start;
720
805
  justify-content: space-between;
@@ -722,6 +807,7 @@ button.danger {
722
807
 
723
808
  .artifact-row-top strong,
724
809
  .artifact-viewer-header h3 {
810
+ min-width: 0;
725
811
  overflow: hidden;
726
812
  text-overflow: ellipsis;
727
813
  }
@@ -752,6 +838,7 @@ button.danger {
752
838
  .artifact-row-preview {
753
839
  display: -webkit-box;
754
840
  overflow: hidden;
841
+ overflow-wrap: anywhere;
755
842
  -webkit-box-orient: vertical;
756
843
  -webkit-line-clamp: 2;
757
844
  }
@@ -808,6 +895,7 @@ button.danger {
808
895
 
809
896
  .artifact-pdf-frame {
810
897
  width: 100%;
898
+ max-width: 100%;
811
899
  min-height: 62vh;
812
900
  border: 1px solid var(--line);
813
901
  border-radius: 14px;
@@ -828,6 +916,8 @@ button.danger {
828
916
 
829
917
  @media (max-width: 960px) {
830
918
  .page {
919
+ margin: 16px auto;
920
+ padding: 0 12px;
831
921
  grid-template-columns: 1fr;
832
922
  }
833
923
 
@@ -876,3 +966,23 @@ button.danger {
876
966
  overflow: auto;
877
967
  }
878
968
  }
969
+
970
+ @media (max-width: 720px) {
971
+ .grid,
972
+ .checks,
973
+ .wrapper-controls {
974
+ grid-template-columns: 1fr;
975
+ }
976
+
977
+ .panel {
978
+ padding: 15px;
979
+ }
980
+
981
+ .artifact-tabs {
982
+ border-radius: 18px;
983
+ }
984
+
985
+ .artifact-tab {
986
+ min-width: 120px;
987
+ }
988
+ }
@@ -17,7 +17,7 @@ function assert(condition, message) {
17
17
  if (!condition) throw new Error(message);
18
18
  }
19
19
 
20
- async function runMock(goal, sessionId) {
20
+ async function runMock(goal, sessionId, { resume = false } = {}) {
21
21
  const config = resolveRuntimeConfig(
22
22
  {
23
23
  provider: "mock",
@@ -26,6 +26,7 @@ async function runMock(goal, sessionId) {
26
26
  goal,
27
27
  commandCwd: workspace,
28
28
  maxSteps: 5,
29
+ resume: resume ? sessionId : "",
29
30
  },
30
31
  {
31
32
  baseDir: runtimeDir,
@@ -38,7 +39,7 @@ async function runMock(goal, sessionId) {
38
39
  allowFileTools: true,
39
40
  sandboxMode: "host",
40
41
  packageInstallPolicy: "block",
41
- sessionId,
42
+ sessionId: resume ? "" : sessionId,
42
43
  }
43
44
  );
44
45
 
@@ -78,11 +79,23 @@ try {
78
79
  "repaired DeepSeek history retained an orphan stale tool message"
79
80
  );
80
81
 
81
- const writeRun = await runMock("Create file: notes/mock-output.txt with a short coding smoke message.", "coding-write");
82
- const written = await fs.readFile(path.join(workspace, "notes/mock-output.txt"), "utf8");
82
+ const writeRun = await runMock("Create notes/hello.md with a short coding smoke message.", "coding-write");
83
+ const written = await fs.readFile(path.join(workspace, "notes/hello.md"), "utf8");
83
84
  assert(written.includes("Created by AgInTiFlow mock mode."), "mock write did not create expected file");
84
85
  assert(writeRun.events.some((event) => event.type === "file.changed"), "write run did not persist file.changed event");
85
86
 
87
+ await runMock("Create notes/resume.md with resumed session content.", "coding-write", { resume: true });
88
+ const resumed = await fs.readFile(path.join(workspace, "notes/resume.md"), "utf8");
89
+ assert(resumed.includes("Created by AgInTiFlow mock mode."), "mock resume did not create a new requested file");
90
+
91
+ let duplicateFailed = false;
92
+ try {
93
+ await runMock("Create notes/hello.md with duplicate content.", "coding-write-duplicate");
94
+ } catch (error) {
95
+ duplicateFailed = /File already exists|Mock tool failed/.test(String(error));
96
+ }
97
+ assert(duplicateFailed, "duplicate mock write did not fail safely");
98
+
86
99
  await runMock("Create file: /workspace/virtual-output.txt with virtual Docker path support.", "coding-write-virtual");
87
100
  const virtualWritten = await fs.readFile(path.join(workspace, "virtual-output.txt"), "utf8");
88
101
  assert(virtualWritten.includes("Created by AgInTiFlow mock mode."), "virtual /workspace path was not mapped safely");
@@ -123,6 +136,8 @@ try {
123
136
  checks: [
124
137
  "deepseek_history_repair",
125
138
  "write_file",
139
+ "duplicate_write_failed",
140
+ "resume_session_write",
126
141
  "virtual_workspace_path",
127
142
  "apply_patch",
128
143
  "block_env",
@@ -74,6 +74,23 @@ try {
74
74
  if (!config.keyStatus?.mock) throw new Error("mock provider is not advertised by /api/config");
75
75
  if (!config.workspace?.enabled) throw new Error("workspace file tools are not advertised by /api/config");
76
76
  if (config.preferences?.preferredWrapper !== "codex") throw new Error("Codex is not the default preferred wrapper");
77
+ if (config.project?.root !== runtimeDir) throw new Error("web project root did not default to launch directory");
78
+ if (config.preferences?.commandCwd !== runtimeDir) throw new Error("commandCwd did not default to project root");
79
+ if (!Array.isArray(config.taskProfiles) || !config.taskProfiles.some((profile) => profile.id === "latex")) {
80
+ throw new Error("task profiles are not advertised by /api/config");
81
+ }
82
+
83
+ const keyStatus = await fetchJson("/api/keys/status");
84
+ if (typeof keyStatus.keyStatus?.deepseek !== "boolean") throw new Error("key status endpoint is invalid");
85
+ if ("localEnvPath" in keyStatus.keyStatus) throw new Error("key status leaked a local env path");
86
+ const savedKey = await fetchJson("/api/keys/deepseek", {
87
+ method: "POST",
88
+ headers: { "Content-Type": "application/json" },
89
+ body: JSON.stringify({ apiKey: "test-deepseek-key-not-real" }),
90
+ });
91
+ if (!savedKey.ok || !savedKey.keyStatus?.deepseek || "apiKey" in savedKey || "key" in savedKey) {
92
+ throw new Error("local key save endpoint returned invalid or sensitive data");
93
+ }
77
94
 
78
95
  const status = await fetchJson("/api/sandbox/status");
79
96
  if (!status.status?.workspaceReadable) throw new Error("sandbox status did not report a readable workspace");
@@ -111,6 +128,30 @@ try {
111
128
  if (run.status !== "finished") throw new Error(`mock run failed: ${run.error || "unknown error"}`);
112
129
  if (!/Mock run complete/.test(run.result)) throw new Error("mock run did not return the expected result");
113
130
 
131
+ const fileRunStart = await fetchJson("/api/runs", {
132
+ method: "POST",
133
+ headers: { "Content-Type": "application/json" },
134
+ body: JSON.stringify({
135
+ provider: "mock",
136
+ routingMode: "manual",
137
+ model: "mock-agent",
138
+ goal: "Create notes/hello.md with safe web API content.",
139
+ commandCwd: runtimeDir,
140
+ sandboxMode: "host",
141
+ packageInstallPolicy: "block",
142
+ allowShellTool: false,
143
+ allowFileTools: true,
144
+ preferredWrapper: "codex",
145
+ maxSteps: 4,
146
+ headless: true,
147
+ taskProfile: "code",
148
+ }),
149
+ });
150
+ const fileRun = await waitForRun(fileRunStart.sessionId);
151
+ if (fileRun.status !== "finished") throw new Error(`mock file run failed: ${fileRun.error || "unknown error"}`);
152
+ const hello = await fs.readFile(path.join(runtimeDir, "notes", "hello.md"), "utf8");
153
+ if (!hello.includes("Created by AgInTiFlow mock mode.")) throw new Error("mock file run did not create requested path");
154
+
114
155
  const chat = await fetchJson(`/api/sessions/${encodeURIComponent(runStart.sessionId)}/chat`);
115
156
  if (!Array.isArray(chat.chat) || chat.chat.length < 2) throw new Error("chat history was not persisted");
116
157
 
@@ -189,6 +230,8 @@ try {
189
230
  ok: true,
190
231
  endpoints: [
191
232
  "/api/config",
233
+ "/api/keys/status",
234
+ "POST /api/keys/:provider",
192
235
  "/api/sandbox/status",
193
236
  "/api/sandbox/preflight",
194
237
  "/api/runs",
@@ -13,6 +13,7 @@ import { evaluateCommandPolicy } from "./command-policy.js";
13
13
  import { redactSensitiveText, redactValue } from "./redaction.js";
14
14
  import { executeWorkspaceTool, summarizeWorkspaceTools, WORKSPACE_TOOL_NAMES } from "./workspace-tools.js";
15
15
  import { normalizeCanvasPayload } from "./artifact-tunnel.js";
16
+ import { getTaskProfile } from "./task-profiles.js";
16
17
 
17
18
  const exec = promisify(execCallback);
18
19
  const BROWSER_TOOLS = new Set(["open_url", "click", "type", "scroll", "press", "back"]);
@@ -108,6 +109,7 @@ export function repairModelMessageHistory(state, config = {}) {
108
109
 
109
110
  function createInitialState(config, sessionId) {
110
111
  const now = new Date().toISOString();
112
+ const taskProfile = getTaskProfile(config.taskProfile);
111
113
  return {
112
114
  sessionId,
113
115
  createdAt: now,
@@ -151,6 +153,7 @@ function createInitialState(config, sessionId) {
151
153
  config.allowWrapperTools
152
154
  ? `External coding-agent wrappers are available as advisory tools only. Use the selected wrapper only: ${normalizeWrapperName(config.preferredWrapper)}. Wrapper status: ${wrapperStatusText()}.`
153
155
  : "External coding-agent wrappers are disabled.",
156
+ `Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
154
157
  "A frontend canvas/artifacts tunnel exists. Use send_to_canvas when important markdown, diffs, screenshots, images, or workspace files should be highlighted in the UI. It is optional and ordinary final text can still go directly to finish.",
155
158
  "For visual-output requests such as draw, plot, graph, chart, diagram, figure, image, or visualization, proactively publish a canvas artifact even when the user does not mention canvas. If workspace file tools are enabled, prefer creating a small SVG or markdown artifact and call send_to_canvas with selected=true.",
156
159
  "For LaTeX/PDF requests, create the needed source/assets, compile with the available allowlisted TeX toolchain, and publish the resulting PDF through send_to_canvas. For subfolder documents, keep outputs beside the source. Use pdflatex-compatible figure formats such as PDF or PNG.",
@@ -172,6 +175,7 @@ function createInitialState(config, sessionId) {
172
175
  config.allowWrapperTools
173
176
  ? `Agent wrappers: selected=${normalizeWrapperName(config.preferredWrapper)}; ${wrapperStatusText()}`
174
177
  : "",
178
+ `Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
175
179
  "Canvas/artifacts tunnel: available through send_to_canvas for optional frontend rendering.",
176
180
  "Visual-output requests should produce a canvas artifact without requiring the user to ask for canvas explicitly.",
177
181
  "LaTeX/PDF requests should produce source artifacts and, when possible, a compiled PDF artifact. For subfolder documents, keep outputs beside the source. For figure-in-document tasks, create PDF/PNG figures that pdflatex can include.",
@@ -248,6 +252,7 @@ function appendChatEntry(state, role, content) {
248
252
  function applyContinuationPrompt(state, config, observers) {
249
253
  if (!config.resume || !config.goal) return;
250
254
 
255
+ const taskProfile = getTaskProfile(config.taskProfile);
251
256
  ensureChatState(state);
252
257
  state.goal = config.goal;
253
258
  state.provider = config.provider;
@@ -271,6 +276,7 @@ function applyContinuationPrompt(state, config, observers) {
271
276
  config.allowWrapperTools
272
277
  ? `Agent wrappers: selected=${normalizeWrapperName(config.preferredWrapper)}; ${wrapperStatusText()}`
273
278
  : "",
279
+ `Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
274
280
  ]
275
281
  .filter(Boolean)
276
282
  .join("\n"),
@@ -754,6 +760,7 @@ export async function runAgent(config) {
754
760
  model: config.model,
755
761
  routingMode: config.routingMode,
756
762
  routeReason: config.routeReason,
763
+ taskProfile: config.taskProfile,
757
764
  commandCwd: config.commandCwd,
758
765
  allowShellTool: config.allowShellTool,
759
766
  allowWrapperTools: config.allowWrapperTools,
@@ -817,6 +824,7 @@ export async function runAgent(config) {
817
824
  plan: state.plan || "",
818
825
  suggestedStartUrl: config.startUrl || "",
819
826
  canvasArtifactsAvailable: true,
827
+ taskProfile: getTaskProfile(config.taskProfile),
820
828
  })}`,
821
829
  });
822
830
 
@@ -899,6 +907,12 @@ export async function runAgent(config) {
899
907
  });
900
908
  }
901
909
 
910
+ if (config.provider === "mock" && toolResult.ok === false && !toolResult.blocked) {
911
+ throw new Error(
912
+ `Mock tool failed: ${toolResult.error || toolResult.reason || `${toolResult.toolName || "tool"} returned ok=false`}`
913
+ );
914
+ }
915
+
902
916
  if (toolResult.done) {
903
917
  state.stepsCompleted = step;
904
918
  state.updatedAt = new Date().toISOString();