@@ -207,10 +207,17 @@
207 207 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
_ = relay.Close(closeCtx)
208 208 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}()
209 209 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
210 210 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
211 211 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
cmd := exec.Command(cfg.CodexBin, cfg.Args...)
212 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Snapshot existing session files before starting the subprocess so
213 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // discovery can distinguish our session from pre-existing ones.
214 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ var preExisting map[string]struct{}
215 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if sessRoot, err := codexSessionsRoot(); err == nil {
216 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ preExisting = snapshotSessionFiles(sessRoot)
217 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
218 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
212 219 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
startedAt := time.Now()
213 220 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
cmd.Env = append(os.Environ(),
214 221 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"SCUTTLEBOT_CONFIG_FILE="+cfg.ConfigFile,
215 222 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"SCUTTLEBOT_URL="+cfg.URL,
216 223 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"SCUTTLEBOT_TOKEN="+cfg.Token,
@@ -221,11 +228,11 @@
221 228 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"SCUTTLEBOT_SESSION_ID="+cfg.SessionID,
222 229 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"SCUTTLEBOT_NICK="+cfg.Nick,
223 230 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"SCUTTLEBOT_ACTIVITY_VIA_BROKER="+boolString(relayActive),
224 231 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
)
225 232 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if relayActive {
226 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- go mirrorSessionLoop(ctx, relay, cfg, startedAt)
233 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ go mirrorSessionLoop(ctx, relay, cfg, startedAt, preExisting)
227 234 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
go presenceLoopPtr(ctx, &relay, cfg.HeartbeatInterval)
228 235 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
229 236 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
230 237 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if !isInteractiveTTY() {
231 238 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
cmd.Stdin = os.Stdin
@@ -401,11 +408,11 @@
401 408 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
fmt.Fprintf(os.Stderr, "codex-relay: reconnected, restarting mirror and input loops\n")
402 409 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
403 410 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
// Restart mirror and input loops with the new connector.
404 411 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
// Use epoch time for mirror so it finds the existing session file
405 412 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
// regardless of when it was last modified.
406 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- go mirrorSessionLoop(ctx, conn, cfg, time.Time{})
413 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ go mirrorSessionLoop(ctx, conn, cfg, time.Time{}, nil)
407 414 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
go relayInputLoop(ctx, conn, cfg, state, ptmx, now)
408 415 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
break
409 416 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
410 417 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
411 418 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
@@ -802,16 +809,16 @@
802 809 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
func defaultSessionID(target string) string {
803 810 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
sum := crc32.ChecksumIEEE([]byte(fmt.Sprintf("%s|%d|%d|%d", target, os.Getpid(), os.Getppid(), time.Now().UnixNano())))
804 811 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return fmt.Sprintf("%08x", sum)
805 812 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
806 813 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
807 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- func mirrorSessionLoop(ctx context.Context, relay sessionrelay.Connector, cfg config, startedAt time.Time) {
814 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func mirrorSessionLoop(ctx context.Context, relay sessionrelay.Connector, cfg config, startedAt time.Time, preExisting map[string]struct{}) {
808 815 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
for {
809 816 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if ctx.Err() != nil {
810 817 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return
811 818 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
812 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- sessionPath, err := discoverSessionPath(ctx, cfg, startedAt)
819 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ sessionPath, err := discoverSessionPath(ctx, cfg, startedAt, preExisting)
813 820 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if err != nil {
814 821 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if ctx.Err() != nil {
815 822 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return
816 823 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
817 824 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
time.Sleep(10 * time.Second)
@@ -834,11 +841,11 @@
834 841 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
835 842 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return
836 843 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
837 844 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
838 845 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
839 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- func discoverSessionPath(ctx context.Context, cfg config, startedAt time.Time) (string, error) {
846 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func discoverSessionPath(ctx context.Context, cfg config, startedAt time.Time, preExisting map[string]struct{}) (string, error) {
840 847 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
root, err := codexSessionsRoot()
841 848 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if err != nil {
842 849 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return "", err
843 850 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
844 851 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
@@ -848,11 +855,11 @@
848 855 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
})
849 856 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
850 857 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
851 858 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
target := filepath.Clean(cfg.TargetCWD)
852 859 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return waitForSessionPath(ctx, func() (string, error) {
853 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- return findLatestSessionPath(root, target, startedAt.Add(-2*time.Second))
860 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return findLatestSessionPath(root, target, startedAt.Add(-2*time.Second), preExisting)
854 861 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
})
855 862 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
856 863 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
857 864 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
func waitForSessionPath(ctx context.Context, find func() (string, error)) (string, error) {
858 865 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
ctx, cancel := context.WithTimeout(ctx, defaultDiscoverWait)
@@ -1102,10 +1109,25 @@
1102 1109 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return strings.TrimSpace(args[i+1])
1103 1110 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
1104 1111 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
1105 1112 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return ""
1106 1113 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
1114 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1115 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // snapshotSessionFiles returns the set of .jsonl file paths currently under root.
1116 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Called before starting the Codex subprocess so discovery can skip pre-existing
1117 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // sessions and deterministically find the one our subprocess creates.
1118 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func snapshotSessionFiles(root string) map[string]struct{} {
1119 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ existing := make(map[string]struct{})
1120 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ _ = filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
1121 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err != nil || d.IsDir() || !strings.HasSuffix(path, ".jsonl") {
1122 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return nil
1123 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
1124 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ existing[path] = struct{}{}
1125 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return nil
1126 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ })
1127 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return existing
1128 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
1107 1129 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
1108 1130 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
func codexSessionsRoot() (string, error) {
1109 1131 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if value := os.Getenv("CODEX_HOME"); value != "" {
1110 1132 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return filepath.Join(value, "sessions"), nil
1111 1133 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
@@ -1135,23 +1157,30 @@
1135 1157 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return "", os.ErrNotExist
1136 1158 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
1137 1159 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return match, nil
1138 1160 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
1139 1161 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
1140 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- // findLatestSessionPath finds the .jsonl file in root whose first entry
1141 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- // timestamp is closest to notBefore — this ensures each relay latches onto
1142 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- // its own subprocess's session when multiple sessions share a CWD.
1143 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- func findLatestSessionPath(root, target string, notBefore time.Time) (string, error) {
1162 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // findLatestSessionPath finds the .jsonl file in root that was created by our
1163 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // subprocess. It uses a pre-existing file snapshot to skip sessions that
1164 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // existed before the subprocess started, then filters by CWD and picks the
1165 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // oldest new match. When preExisting is nil (reconnect), it falls back to
1166 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // accepting any file whose timestamp is >= notBefore.
1167 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func findLatestSessionPath(root, target string, notBefore time.Time, preExisting map[string]struct{}) (string, error) {
1144 1168 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
type candidate struct {
1145 1169 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
path string
1146 1170 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
ts time.Time
1147 1171 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
1148 1172 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
var candidates []candidate
1149 1173 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
1150 1174 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
err := filepath.WalkDir(root, func(path string, d os.DirEntry, walkErr error) error {
1151 1175 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if walkErr != nil || d.IsDir() || !strings.HasSuffix(path, ".jsonl") {
1152 1176 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return nil
1177 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
1178 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if preExisting != nil {
1179 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if _, existed := preExisting[path]; existed {
1180 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return nil
1181 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
1153 1182 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
1154 1183 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
meta, ts, err := readSessionMeta(path)
1155 1184 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if err != nil {
1156 1185 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return nil
1157 1186 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
@@ -1168,29 +1197,16 @@
1168 1197 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return "", err
1169 1198 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
1170 1199 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if len(candidates) == 0 {
1171 1200 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return "", os.ErrNotExist
1172 1201 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
1173 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- // Pick the session whose start time is closest to notBefore.
1174 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- // When multiple relays start near-simultaneously, each relay's
1175 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- // notBefore is slightly different, so each matches its own session.
1176 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- best := 0
1177 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- bestDelta := candidates[0].ts.Sub(notBefore)
1178 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- for i := 1; i < len(candidates); i++ {
1179 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- delta := candidates[i].ts.Sub(notBefore)
1180 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- if delta < 0 {
1181 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- delta = -delta
1182 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
1183 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- if bestDelta < 0 {
1184 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- bestDelta = -bestDelta
1185 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
1186 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- if delta < bestDelta {
1187 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- best = i
1188 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- bestDelta = candidates[i].ts.Sub(notBefore)
1189 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
1190 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
1191 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- return candidates[best].path, nil
1202 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Pick the oldest new session — the first file created after our
1203 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // subprocess started is most likely ours.
1204 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ sort.Slice(candidates, func(i, j int) bool {
1205 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return candidates[i].ts.Before(candidates[j].ts)
1206 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ })
1207 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return candidates[0].path, nil
1192 1208 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
1193 1209 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
1194 1210 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
func readSessionMeta(path string) (sessionMetaPayload, time.Time, error) {
1195 1211 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
file, err := os.Open(path)
1196 1212 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if err != nil {
1197 1213 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!